Compare commits

...

12 Commits

Author SHA1 Message Date
Bernd Storath 4890bb28e5 Bump version to 15.0.0-beta.8 2025-03-12 13:47:46 +01:00
Bernd Storath c3dbd3a815 Fix: Add ui port to template (#1735)
* add ui port to template

* update changelog
2025-03-12 13:44:45 +01:00
Bernd Storath fc480df910 Fix: Update ip outside of cidr (#1733)
* update packages

* check if ip is included on update

* update package manager
2025-03-12 12:47:12 +01:00
dependabot[bot] b3bd2502af build(deps): bump nuxt from 3.15.4 to 3.16.0 in /src (#1727)
Bumps [nuxt](https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt) from 3.15.4 to 3.16.0.
- [Release notes](https://github.com/nuxt/nuxt/releases)
- [Commits](https://github.com/nuxt/nuxt/commits/v3.16.0/packages/nuxt)

---
updated-dependencies:
- dependency-name: nuxt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-10 12:26:21 +01:00
dependabot[bot] eb5ad91022 build(deps-dev): bump eslint from 9.21.0 to 9.22.0 in /src (#1726)
Bumps [eslint](https://github.com/eslint/eslint) from 9.21.0 to 9.22.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.21.0...v9.22.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-10 08:11:31 +01:00
dependabot[bot] f2955a1278 build(deps): bump @nuxtjs/i18n from 9.2.1 to 9.3.1 in /src (#1728)
Bumps [@nuxtjs/i18n](https://github.com/nuxt-modules/i18n) from 9.2.1 to 9.3.1.
- [Release notes](https://github.com/nuxt-modules/i18n/releases)
- [Changelog](https://github.com/nuxt-modules/i18n/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nuxt-modules/i18n/compare/v9.2.1...v9.3.1)

---
updated-dependencies:
- dependency-name: "@nuxtjs/i18n"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-10 07:34:37 +01:00
dependabot[bot] 1b76c066e0 build(deps-dev): bump @nuxt/eslint from 1.1.0 to 1.2.0 in /src (#1729)
Bumps [@nuxt/eslint](https://github.com/nuxt/eslint/tree/HEAD/packages/module) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/nuxt/eslint/releases)
- [Commits](https://github.com/nuxt/eslint/commits/v1.2.0/packages/module)

---
updated-dependencies:
- dependency-name: "@nuxt/eslint"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-10 07:27:29 +01:00
Bernd Storath 5b68cc7311 Feat: Confirm setup password (#1722)
confirm setup password
2025-03-07 16:05:40 +01:00
Bernd Storath 0597470f4c Bump version to 15.0.0-beta.7 2025-03-07 15:00:12 +01:00
Bernd Storath 159a51cff4 Feat: Global config override (#1720)
* be able to change dns. implement global override

* link donate to readme

* implement global config for allowed ips

* change translations, fix generation

* improve docs
2025-03-07 14:59:06 +01:00
Bernd Storath 9fc6ebafb3 Bump version to 15.0.0-beta.6 2025-03-07 12:02:03 +01:00
Bernd Storath 9a029eeb23 improve docs, add version script 2025-03-07 12:02:02 +01:00
36 changed files with 1654 additions and 1742 deletions
+2
View File
@@ -24,6 +24,8 @@ This update is an entire rewrite to make it even easier to set up your own VPN.
- Deprecated Dockerless Installations - Deprecated Dockerless Installations
- Added Docker Volume Mount (`/lib/modules`) - Added Docker Volume Mount (`/lib/modules`)
- Removed ARMv6 and ARMv7 support - 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 ## [14.0.0] - 2024-09-04
+28 -76
View File
@@ -42,8 +42,21 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
> [!NOTE] > [!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) > 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/) - [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/) - [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 ## 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 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/) --> ### Donate
<!-- 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
Are you enjoying this project? Consider donating. 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) ☕ 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 | ### Dev Server
| ---------- | --------- | ----------- | ------------------------------ |
| `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 |
## Updating This starts the development server with docker
To update to the latest version, simply run:
```shell ```shell
docker stop wg-easy pnpm dev
docker rm wg-easy
docker pull ghcr.io/wg-easy/wg-easy
``` ```
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: If you add something that should be auto-importable and VSCode complains, run:
`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.
## Common Use Cases ```shell
cd src
- [Using WireGuard-Easy with Pi-Hole](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-Pi-Hole) pnpm install
- [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).
-->
## License ## License
+2 -2
View File
@@ -9,7 +9,7 @@ services:
# - HOST=0.0.0.0 # - HOST=0.0.0.0
# - INSECURE=false # - INSECURE=false
image: ghcr.io/wg-easy/wg-easy image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy container_name: wg-easy
networks: networks:
wg: wg:
@@ -25,7 +25,7 @@ services:
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
- SYS_MODULE - SYS_MODULE
# - NET_RAW # ⚠️ Uncomment if using Podman # - NET_RAW # ⚠️ Uncomment if using Podman Compose
sysctls: sysctls:
- net.ipv4.ip_forward=1 - net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1 - net.ipv4.conf.all.src_valid_mark=1
@@ -3,3 +3,9 @@ title: Optional Configuration
--- ---
TODO 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/) -->
+7
View File
@@ -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 2. Download docker compose file
```shell ```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` 3. Start `wg-easy`
@@ -54,3 +54,18 @@ TODO
Open your browser and navigate to `https://<your-domain>:51821` or `https://<your-ip>:51821`. Open your browser and navigate to `https://<your-domain>:51821` or `https://<your-ip>:51821`.
Follow the instructions to set up your WireGuard VPN. 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
+5
View File
@@ -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`
+5
View File
@@ -0,0 +1,5 @@
---
title: NGINX
---
TODO
+19 -2
View File
@@ -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: Create a file `/etc/containers/systemd/wg-easy/wg-easy.container` with the following content:
<!-- ref: major version -->
```ini ```ini
[Container] [Container]
ContainerName=wg-easy 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 Volume=/etc/containers/volumes/wg-easy:/etc/wireguard:Z
Network=wg-easy.network Network=wg-easy.network
PublishPort=51820:51820/udp PublishPort=51820:51820/udp
PublishPort=51821:51821/tcp 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=NET_ADMIN
AddCapability=SYS_MODULE AddCapability=SYS_MODULE
AddCapability=NET_RAW 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 1. PostUp
```shell ```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 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 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: improve docs after better nftables support
TODO: fix accept web ui port TODO: fix accept web ui port
@@ -0,0 +1,5 @@
---
title: Traefik
---
TODO
+3 -2
View File
@@ -4,7 +4,8 @@
"scripts": { "scripts": {
"dev": "docker compose -f docker-compose.dev.yml up --build", "dev": "docker compose -f docker-compose.dev.yml up --build",
"build": "docker build -t wg-easy .", "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"
} }
+54
View File
@@ -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"
@@ -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>
+1 -1
View File
@@ -24,7 +24,7 @@
· ·
<a <a
class="hover:underline" class="hover:underline"
href="https://github.com/sponsors/WeeJeWel" href="https://github.com/wg-easy/wg-easy#donate"
target="_blank" target="_blank"
>{{ $t('layout.donate') }}</a >{{ $t('layout.donate') }}</a
> >
+1 -1
View File
@@ -27,7 +27,7 @@
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormHeading :description="$t('admin.config.dnsDesc')">{{ <FormHeading :description="$t('admin.config.dnsDesc')">{{
$t('admin.config.dns') $t('general.dns')
}}</FormHeading> }}</FormHeading>
<FormArrayField v-model="data.defaultDns" name="defaultDns" /> <FormArrayField v-model="data.defaultDns" name="defaultDns" />
</FormGroup> </FormGroup>
+18 -9
View File
@@ -41,21 +41,26 @@
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormHeading :description="$t('client.allowedIpsDesc')">{{ <FormHeading :description="$t('client.allowedIpsDesc')">
$t('general.allowedIps') {{ $t('general.allowedIps') }}
}}</FormHeading> </FormHeading>
<FormArrayField v-model="data.allowedIps" name="allowedIps" /> <FormNullArrayField v-model="data.allowedIps" name="allowedIps" />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormHeading :description="$t('client.serverAllowedIpsDesc')">{{ <FormHeading :description="$t('client.serverAllowedIpsDesc')">
$t('client.serverAllowedIps') {{ $t('client.serverAllowedIps') }}
}}</FormHeading> </FormHeading>
<FormArrayField <FormArrayField
v-model="data.serverAllowedIps" v-model="data.serverAllowedIps"
name="serverAllowedIps" name="serverAllowedIps"
/> />
</FormGroup> </FormGroup>
<FormGroup></FormGroup> <FormGroup>
<FormHeading :description="$t('client.dnsDesc')">
{{ $t('general.dns') }}
</FormHeading>
<FormNullArrayField v-model="data.dns" name="dns" />
</FormGroup>
<FormGroup> <FormGroup>
<FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading> <FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading>
<FormNumberField <FormNumberField
@@ -142,8 +147,12 @@ const _submit = useSubmit(
method: 'post', method: 'post',
}, },
{ {
revert: async () => { revert: async (success) => {
if (success) {
await navigateTo('/'); await navigateTo('/');
} else {
await revert();
}
}, },
} }
); );
+1 -1
View File
@@ -40,7 +40,7 @@
id="confirm-password" id="confirm-password"
v-model="confirmPassword" v-model="confirmPassword"
autocomplete="new-password" autocomplete="new-password"
:label="$t('me.confirmPassword')" :label="$t('general.confirmPassword')"
/> />
<FormActionField <FormActionField
type="submit" type="submit"
+14 -1
View File
@@ -20,6 +20,14 @@
:label="$t('general.password')" :label="$t('general.password')"
/> />
</div> </div>
<div class="flex flex-col">
<FormPasswordField
id="confirmPassword"
v-model="confirmPassword"
autocomplete="new-password"
:label="$t('general.confirmPassword')"
/>
</div>
<div> <div>
<BaseButton @click="submit">{{ $t('setup.createAccount') }}</BaseButton> <BaseButton @click="submit">{{ $t('setup.createAccount') }}</BaseButton>
</div> </div>
@@ -37,6 +45,7 @@ setupStore.setStep(2);
const username = ref<null | string>(null); const username = ref<null | string>(null);
const password = ref<string>(''); const password = ref<string>('');
const confirmPassword = ref<string>('');
const _submit = useSubmit( const _submit = useSubmit(
'/api/setup/2', '/api/setup/2',
@@ -54,6 +63,10 @@ const _submit = useSubmit(
); );
function submit() { function submit() {
return _submit({ username: username.value, password: password.value }); return _submit({
username: username.value,
password: password.value,
confirmPassword: confirmPassword.value,
});
} }
</script> </script>
+12 -10
View File
@@ -14,8 +14,7 @@
"email": "E-Mail" "email": "E-Mail"
}, },
"me": { "me": {
"currentPassword": "Current Password", "currentPassword": "Current Password"
"confirmPassword": "Confirm Password"
}, },
"general": { "general": {
"name": "Name", "name": "Name",
@@ -25,13 +24,15 @@
"updatePassword": "Update Password", "updatePassword": "Update Password",
"mtu": "MTU", "mtu": "MTU",
"allowedIps": "Allowed IPs", "allowedIps": "Allowed IPs",
"dns": "DNS",
"persistentKeepalive": "Persistent Keepalive", "persistentKeepalive": "Persistent Keepalive",
"logout": "Logout", "logout": "Logout",
"continue": "Continue", "continue": "Continue",
"host": "Host", "host": "Host",
"port": "Port", "port": "Port",
"yes": "Yes", "yes": "Yes",
"no": "No" "no": "No",
"confirmPassword": "Confirm Password"
}, },
"setup": { "setup": {
"welcome": "Welcome to your first setup of wg-easy !", "welcome": "Welcome to your first setup of wg-easy !",
@@ -95,13 +96,14 @@
"noPrivKey": "This client has no known private key. Cannot create Configuration.", "noPrivKey": "This client has no known private key. Cannot create Configuration.",
"showQR": "Show QR Code", "showQR": "Show QR Code",
"downloadConfig": "Download Configuration", "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", "serverAllowedIpsDesc": "Which IPs the server will route to the client",
"mtuDesc": "Sets the maximum transmission unit (packet size) for the VPN tunnel", "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", "hooks": "Hooks",
"hooksDescription": "Hooks only work with wg-quick", "hooksDescription": "Hooks only work with wg-quick",
"hooksLeaveEmpty": "Only for wg-quick. Otherwise, leave it empty" "hooksLeaveEmpty": "Only for wg-quick. Otherwise, leave it empty",
"dnsDesc": "DNS server clients will use (overrides global config)"
}, },
"dialog": { "dialog": {
"change": "Change", "change": "Change",
@@ -121,6 +123,7 @@
"sectionGeneral": "General", "sectionGeneral": "General",
"sectionAdvanced": "Advanced", "sectionAdvanced": "Advanced",
"noItems": "No items", "noItems": "No items",
"nullNoItems": "No items. Using global config",
"add": "Add" "add": "Add"
}, },
"admin": { "admin": {
@@ -139,11 +142,10 @@
"connection": "Connection", "connection": "Connection",
"hostDesc": "Public hostname clients will connect to (invalidates config)", "hostDesc": "Public hostname clients will connect to (invalidates config)",
"portDesc": "Public UDP port clients will connect to (invalidates config)", "portDesc": "Public UDP port clients will connect to (invalidates config)",
"allowedIpsDesc": "Allowed IPs clients will use (invalidates config)", "allowedIpsDesc": "Allowed IPs clients will use (global config)",
"dns": "DNS", "dnsDesc": "DNS server clients will use (global config)",
"dnsDesc": "DNS server clients will use (invalidates config)", "mtuDesc": "MTU clients will use (only for new clients)",
"mtuDesc": "MTU clients will use (invalidates config)", "persistentKeepaliveDesc": "Interval in seconds to send keepalives to the server. 0 = disabled (only for new clients)"
"persistentKeepaliveDesc": "Interval in seconds to send keepalives to the server. 0 = disabled (invalidates config)"
}, },
"interface": { "interface": {
"cidrSuccess": "Changed CIDR", "cidrSuccess": "Changed CIDR",
+8 -8
View File
@@ -1,6 +1,6 @@
{ {
"name": "wg-easy", "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.", "description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -20,8 +20,8 @@
"dependencies": { "dependencies": {
"@eschricht/nuxt-color-mode": "^1.1.5", "@eschricht/nuxt-color-mode": "^1.1.5",
"@libsql/client": "^0.14.0", "@libsql/client": "^0.14.0",
"@nuxtjs/i18n": "^9.2.1", "@nuxtjs/i18n": "^9.3.1",
"@nuxtjs/tailwindcss": "^6.13.1", "@nuxtjs/tailwindcss": "^6.13.2",
"@pinia/nuxt": "^0.10.1", "@pinia/nuxt": "^0.10.1",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
@@ -36,7 +36,7 @@
"is-ip": "^5.0.1", "is-ip": "^5.0.1",
"js-sha256": "^0.11.0", "js-sha256": "^0.11.0",
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"nuxt": "^3.15.4", "nuxt": "^3.16.0",
"pinia": "^3.0.1", "pinia": "^3.0.1",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"radix-vue": "^1.9.17", "radix-vue": "^1.9.17",
@@ -48,17 +48,17 @@
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint": "1.1.0", "@nuxt/eslint": "1.2.0",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"drizzle-kit": "^0.30.5", "drizzle-kit": "^0.30.5",
"eslint": "^9.21.0", "eslint": "^9.22.0",
"eslint-config-prettier": "^10.0.2", "eslint-config-prettier": "^10.1.1",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.11",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vue-tsc": "^2.2.8" "vue-tsc": "^2.2.8"
}, },
"packageManager": "pnpm@10.5.2" "packageManager": "pnpm@10.6.2"
} }
+1225 -1602
View File
File diff suppressed because it is too large Load Diff
@@ -12,11 +12,11 @@ CREATE TABLE `clients_table` (
`public_key` text NOT NULL, `public_key` text NOT NULL,
`pre_shared_key` text NOT NULL, `pre_shared_key` text NOT NULL,
`expires_at` text, `expires_at` text,
`allowed_ips` text NOT NULL, `allowed_ips` text,
`server_allowed_ips` text NOT NULL, `server_allowed_ips` text NOT NULL,
`persistent_keepalive` integer NOT NULL, `persistent_keepalive` integer NOT NULL,
`mtu` integer NOT NULL, `mtu` integer NOT NULL,
`dns` text NOT NULL, `dns` text,
`enabled` integer NOT NULL, `enabled` integer NOT NULL,
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, `created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, `updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
@@ -1,7 +1,7 @@
{ {
"version": "6", "version": "6",
"dialect": "sqlite", "dialect": "sqlite",
"id": "383501e4-f8de-4413-847f-a9082f6dc398", "id": "8c2af02b-c4bd-4880-a9ad-b38805636208",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"tables": { "tables": {
"clients_table": { "clients_table": {
@@ -106,7 +106,7 @@
"name": "allowed_ips", "name": "allowed_ips",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": false,
"autoincrement": false "autoincrement": false
}, },
"server_allowed_ips": { "server_allowed_ips": {
@@ -134,7 +134,7 @@
"name": "dns", "name": "dns",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": false,
"autoincrement": false "autoincrement": false
}, },
"enabled": { "enabled": {
@@ -1,6 +1,6 @@
{ {
"id": "bf316694-e2ce-4e29-bd66-ce6c0a9d3c90", "id": "a61263b1-9af1-4d2e-99e9-80d08127b545",
"prevId": "383501e4-f8de-4413-847f-a9082f6dc398", "prevId": "8c2af02b-c4bd-4880-a9ad-b38805636208",
"version": "6", "version": "6",
"dialect": "sqlite", "dialect": "sqlite",
"tables": { "tables": {
@@ -106,7 +106,7 @@
"name": "allowed_ips", "name": "allowed_ips",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": false,
"autoincrement": false "autoincrement": false
}, },
"server_allowed_ips": { "server_allowed_ips": {
@@ -134,7 +134,7 @@
"name": "dns", "name": "dns",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": false,
"autoincrement": false "autoincrement": false
}, },
"enabled": { "enabled": {
@@ -5,14 +5,14 @@
{ {
"idx": 0, "idx": 0,
"version": "6", "version": "6",
"when": 1741335144499, "when": 1741355094140,
"tag": "0000_short_skin", "tag": "0000_short_skin",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 1, "idx": 1,
"version": "6", "version": "6",
"when": 1741335153054, "when": 1741355098159,
"tag": "0001_classy_the_stranger", "tag": "0001_classy_the_stranger",
"breakpoints": true "breakpoints": true
} }
@@ -3,6 +3,8 @@ import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { oneTimeLink, user } from '../../schema'; import { oneTimeLink, user } from '../../schema';
/** null means use value from userConfig */
export const client = sqliteTable('clients_table', { export const client = sqliteTable('clients_table', {
id: int().primaryKey({ autoIncrement: true }), id: int().primaryKey({ autoIncrement: true }),
userId: int('user_id') userId: int('user_id')
@@ -22,13 +24,13 @@ export const client = sqliteTable('clients_table', {
publicKey: text('public_key').notNull(), publicKey: text('public_key').notNull(),
preSharedKey: text('pre_shared_key').notNull(), preSharedKey: text('pre_shared_key').notNull(),
expiresAt: text('expires_at'), 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' }) serverAllowedIps: text('server_allowed_ips', { mode: 'json' })
.$type<string[]>() .$type<string[]>()
.notNull(), .notNull(),
persistentKeepalive: int('persistent_keepalive').notNull(), persistentKeepalive: int('persistent_keepalive').notNull(),
mtu: int().notNull(), mtu: int().notNull(),
dns: text({ mode: 'json' }).$type<string[]>().notNull(), dns: text({ mode: 'json' }).$type<string[]>(),
enabled: int({ mode: 'boolean' }).notNull(), enabled: int({ mode: 'boolean' }).notNull(),
createdAt: text('created_at') createdAt: text('created_at')
.notNull() .notNull()
@@ -1,5 +1,5 @@
import { eq, sql } from 'drizzle-orm'; import { eq, sql } from 'drizzle-orm';
import { parseCidr } from 'cidr-tools'; import { containsCidr, parseCidr } from 'cidr-tools';
import { client } from './schema'; import { client } from './schema';
import type { import type {
ClientCreateFromExistingType, ClientCreateFromExistingType,
@@ -115,8 +115,6 @@ export class ClientService {
ipv4Address, ipv4Address,
ipv6Address, ipv6Address,
mtu: clientConfig.defaultMtu, mtu: clientConfig.defaultMtu,
allowedIps: clientConfig.defaultAllowedIps,
dns: clientConfig.defaultDns,
persistentKeepalive: clientConfig.defaultPersistentKeepalive, persistentKeepalive: clientConfig.defaultPersistentKeepalive,
serverAllowedIps: [], serverAllowedIps: [],
enabled: true, enabled: true,
@@ -134,7 +132,27 @@ export class ClientService {
} }
update(id: ID, data: UpdateClientType) { 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({ async createFromExisting({
@@ -61,11 +61,11 @@ export const ClientUpdateSchema = schemaForType<UpdateClientType>()(
postUp: HookSchema, postUp: HookSchema,
preDown: HookSchema, preDown: HookSchema,
postDown: HookSchema, postDown: HookSchema,
allowedIps: AllowedIpsSchema, allowedIps: AllowedIpsSchema.nullable(),
serverAllowedIps: serverAllowedIps, serverAllowedIps: serverAllowedIps,
mtu: MtuSchema, mtu: MtuSchema,
persistentKeepalive: PersistentKeepaliveSchema, persistentKeepalive: PersistentKeepaliveSchema,
dns: DnsSchema, dns: DnsSchema.nullable(),
}) })
); );
@@ -26,9 +26,14 @@ export const UserLoginSchema = z.object({
remember: remember, remember: remember,
}); });
export const UserSetupSchema = z.object({ export const UserSetupSchema = z
.object({
username: username, username: username,
password: password, password: password,
confirmPassword: password,
})
.refine((val) => val.password === val.confirmPassword, {
message: t('zod.user.passwordMatch'),
}); });
const name = z const name = z
+12
View File
@@ -15,4 +15,16 @@ export const OLD_ENV = {
export const WG_ENV = { export const WG_ENV = {
/** UI is hosted on HTTP instead of HTTPS */ /** UI is hosted on HTTP instead of HTTPS */
INSECURE: process.env.INSECURE === 'true', 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;
}
+2
View File
@@ -15,6 +15,7 @@ export function template(templ: string, values: Record<string, string>) {
* - ipv6Cidr: IPv6 CIDR * - ipv6Cidr: IPv6 CIDR
* - device: Network device * - device: Network device
* - port: Port number * - port: Port number
* - uiPort: UI port number
*/ */
export function iptablesTemplate(templ: string, wgInterface: InterfaceType) { export function iptablesTemplate(templ: string, wgInterface: InterfaceType) {
return template(templ, { return template(templ, {
@@ -22,5 +23,6 @@ export function iptablesTemplate(templ: string, wgInterface: InterfaceType) {
ipv6Cidr: wgInterface.ipv6Cidr, ipv6Cidr: wgInterface.ipv6Cidr,
device: wgInterface.device, device: wgInterface.device,
port: wgInterface.port.toString(), port: wgInterface.port.toString(),
uiPort: WG_ENV.PORT,
}); });
} }
+2 -2
View File
@@ -59,13 +59,13 @@ PostDown = ${iptablesTemplate(hooks.postDown, wgInterface)}`;
return `[Interface] return `[Interface]
PrivateKey = ${client.privateKey} PrivateKey = ${client.privateKey}
Address = ${client.ipv4Address}/${cidr4Block}, ${client.ipv6Address}/${cidr6Block} Address = ${client.ipv4Address}/${cidr4Block}, ${client.ipv6Address}/${cidr6Block}
DNS = ${client.dns.join(', ')} DNS = ${(client.dns ?? userConfig.defaultDns).join(', ')}
MTU = ${client.mtu} MTU = ${client.mtu}
${hookLines.length ? `${hookLines.join('\n')}\n` : ''} ${hookLines.length ? `${hookLines.join('\n')}\n` : ''}
[Peer] [Peer]
PublicKey = ${wgInterface.publicKey} PublicKey = ${wgInterface.publicKey}
PresharedKey = ${client.preSharedKey} PresharedKey = ${client.preSharedKey}
AllowedIPs = ${client.allowedIps.join(', ')} AllowedIPs = ${(client.allowedIps ?? userConfig.defaultAllowedIps).join(', ')}
PersistentKeepalive = ${client.persistentKeepalive} PersistentKeepalive = ${client.persistentKeepalive}
Endpoint = ${userConfig.host}:${userConfig.port}`; Endpoint = ${userConfig.host}:${userConfig.port}`;
}, },