Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7964dc7993 | |||
| 0ac5d7d461 | |||
| 826914a4f3 | |||
| 261da431e7 | |||
| 94b33abf5e | |||
| 8325056ccc | |||
| 81a1b2c907 | |||
| fc8f89fb83 | |||
| d846c7745f | |||
| 61c6fd6c02 | |||
| abe5708058 | |||
| 626339bddb | |||
| 381ae23c07 | |||
| 52382d1d7a | |||
| 68e5216d4b | |||
| ceff95b336 | |||
| 782d1c215f | |||
| e8e26cfe10 | |||
| 400d4d992e | |||
| b08df55321 | |||
| b26a8110e0 | |||
| 692f550596 | |||
| badae8b8e4 | |||
| 7f89bde99e | |||
| 326717444b | |||
| 4e4bfc75e3 | |||
| 5c97a8ba73 | |||
| cba7a160ea | |||
| 4a75e1379d | |||
| 10a140d188 |
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
|
||||
@@ -18,10 +18,10 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
os: ubuntu-24.04-arm
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
# - platform: linux/arm/v7
|
||||
# os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
needs: docker-build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
contents: write
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
|
||||
@@ -25,10 +25,10 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
os: ubuntu-24.04-arm
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
# - platform: linux/arm/v7
|
||||
# os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: master
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
needs: docker-build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
contents: write
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
os: ubuntu-24.04-arm
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
# - platform: linux/arm/v7
|
||||
# os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
|
||||
@@ -26,10 +26,10 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
os: ubuntu-24.04-arm
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
# - platform: linux/arm/v7
|
||||
# os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
needs: docker-build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
contents: write
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
Vendored
-1
@@ -3,7 +3,6 @@
|
||||
"aaron-bond.better-comments",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"antfu.goto-alias",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"esbenp.prettier-vscode",
|
||||
"yoavbls.pretty-ts-errors",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
|
||||
+13
-5
@@ -7,19 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## Added
|
||||
## [15.2.0] - 2026-01-12
|
||||
|
||||
### Added
|
||||
|
||||
- AmneziaWG integration (https://github.com/wg-easy/wg-easy/pull/2102, https://github.com/wg-easy/wg-easy/pull/2226)
|
||||
- Search / filter box (https://github.com/wg-easy/wg-easy/pull/2170)
|
||||
- `INIT_ALLOWED_IPS` env var (https://github.com/wg-easy/wg-easy/pull/2164)
|
||||
- Show client endpoint (https://github.com/wg-easy/wg-easy/pull/2058)
|
||||
- Add option to view and copy config (https://github.com/wg-easy/wg-easy/pull/2289)
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- Fix download as conf.txt (https://github.com/wg-easy/wg-easy/pull/2269)
|
||||
- Clean filename for OTL download (https://github.com/wg-easy/wg-easy/pull/2253)
|
||||
- Text color in admin menu in light mode (https://github.com/wg-easy/wg-easy/pull/2307)
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
|
||||
- Allow lower MTU (https://github.com/wg-easy/wg-easy/pull/2228)
|
||||
- Use /32 and /128 for client Cidr (https://github.com/wg-easy/wg-easy/pull/2217)
|
||||
@@ -27,11 +31,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Publish on Codeberg (https://github.com/wg-easy/wg-easy/pull/2160)
|
||||
- Allow empty DNS (https://github.com/wg-easy/wg-easy/pull/2052, https://github.com/wg-easy/wg-easy/pull/2057)
|
||||
- Don't include keys in API responses (https://github.com/wg-easy/wg-easy/pull/2015)
|
||||
- Try all QR ecc levels (https://github.com/wg-easy/wg-easy/pull/2288)
|
||||
- Update OneTimeLink expiry on reuse (https://github.com/wg-easy/wg-easy/pull/2370)
|
||||
- Removed ARMv7 support (https://github.com/wg-easy/wg-easy/pull/2369)
|
||||
|
||||
## Docs
|
||||
### Docs
|
||||
|
||||
- Add AdGuard Home (https://github.com/wg-easy/wg-easy/pull/2175)
|
||||
- Add Routed (No NAT) docs (https://github.com/wg-easy/wg-easy/pull/2181)
|
||||
- Add Routed (No NAT) docs (https://github.com/wg-easy/wg-easy/pull/2181, https://github.com/wg-easy/wg-easy/pull/2380)
|
||||
- Add AmneziaWG docs (https://github.com/wg-easy/wg-easy/pull/2108, https://github.com/wg-easy/wg-easy/pull/2292)
|
||||
|
||||
## [15.1.0] - 2025-07-01
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
title: AmneziaWG
|
||||
---
|
||||
|
||||
Experimental support for AmneziaWG can be enabled by setting the `EXPERIMENTAL_AWG` environment variable to `true`. This feature is still under development and may change in future releases.
|
||||
## Introduction
|
||||
|
||||
**AmneziaWG** is a modified version of the WireGuard protocol with enhanced traffic obfuscation capabilities. AmneziaWG's primary goal is to counter deep packet inspection (DPI) systems and bypass VPN blocking.
|
||||
|
||||
AmneziaWG adds multi-level transport-layer obfuscation by:
|
||||
|
||||
@@ -12,6 +14,12 @@ AmneziaWG adds multi-level transport-layer obfuscation by:
|
||||
|
||||
These measures make it harder for third parties to analyze or identify your traffic, enhancing both privacy and security.
|
||||
|
||||
## Activating AmneziaWG
|
||||
|
||||
You must install the [AmneziaWG kernel module](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module) on the host system.
|
||||
|
||||
Experimental support for AmneziaWG can be enabled by setting the `EXPERIMENTAL_AWG` environment variable to `true`. Starting from wg-easy version 16, this setting will be enabled by default. This feature is still under development and may change in future releases.
|
||||
|
||||
When enabled, wg-easy will automatically detect whether the AmneziaWG kernel module is available. If it is not, the system will fall back to the standard WireGuard module.
|
||||
|
||||
To override this automatic detection, set the `OVERRIDE_AUTO_AWG` environment variable. By default, this variable is unset.
|
||||
@@ -21,17 +29,51 @@ Possible values:
|
||||
- `awg` — Force use of AmneziaWG
|
||||
- `wg` — Force use of standard WireGuard
|
||||
|
||||
To be able to connect to wg-easy if AmneziaWG is enabled, you must have a AmneziaWG-compatible client.
|
||||
## AmneziaWG Parameters
|
||||
|
||||
Parameter descriptions can be found in the [AmneziaWG documentation](https://docs.amnezia.org/documentation/amnezia-wg) and on the [kernel module page](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module).
|
||||
|
||||
All parameters except I1-I5 will be set at first startup. For information on how to set I1-I5 parameters, refer to the [AmneziaWG documentation](https://docs.amnezia.org/documentation/instructions/new-amneziawg-selfhosted/#how-to-extract-a-protocol-signature-for-amneziawg-15-manually).
|
||||
|
||||
If a parameter is not set, it will not be added to the configuration. If all AmneziaWG-specific parameters are absent, AmneziaWG will be fully compatible with standard WireGuard.
|
||||
|
||||
### Parameter Compatibility Table
|
||||
|
||||
| Parameter | Can differ between server and client | Configurable on server | Configurable on client |
|
||||
| --------- | ------------------------------------ | ---------------------- | ----------------------- |
|
||||
| Jc | ✅ Yes | ✅ | ✅ |
|
||||
| Jmin | ✅ Yes | ✅ | ✅ |
|
||||
| Jmax | ✅ Yes | ✅ | ✅ |
|
||||
| S1-S4 | ❌ No, must match | ✅ | ❌ (copied from server) |
|
||||
| H1-H4 | ❌ No, must match | ✅ | ❌ (copied from server) |
|
||||
| I1-I5 | ✅ Yes | ✅ | ✅ |
|
||||
|
||||
## Client Applications
|
||||
|
||||
To be able to connect to wg-easy if AmneziaWG is enabled, you must have an AmneziaWG-compatible client. Currently, only WG Tunnel and Amnezia VPN supports AmneziaWG 1.5/2.0! AmneziaWG clients require building from source code.
|
||||
|
||||
Android:
|
||||
|
||||
- [AmneziaWG](https://play.google.com/store/apps/details?id=org.amnezia.awg) - Official Client
|
||||
- [Amnezia VPN](https://play.google.com/store/apps/details?id=org.amnezia.vpn) - Amnezia VPN Official Client
|
||||
- [AmneziaWG](https://play.google.com/store/apps/details?id=org.amnezia.awg) - AmneziaWG Official Client
|
||||
- [WG Tunnel](https://play.google.com/store/apps/details?id=com.zaneschepke.wireguardautotunnel) - Third Party Client
|
||||
|
||||
iOS and macOS:
|
||||
|
||||
- [AmneziaWG](https://apps.apple.com/us/app/amneziawg/id6478942365) - Official Client
|
||||
- [Amnezia VPN](https://apps.apple.com/us/app/amneziavpn/id1600529900) - Amnezia VPN Official Client
|
||||
- [AmneziaWG](https://apps.apple.com/us/app/amneziawg/id6478942365) - AmneziaWG Official Client
|
||||
|
||||
Windows:
|
||||
|
||||
- [AmneziaWG](https://github.com/amnezia-vpn/amneziawg-windows-client/releases) - Official Client
|
||||
- [Amnezia VPN](https://amnezia.org/downloads) - Amnezia VPN Official Client
|
||||
- [AmneziaWG](https://github.com/amnezia-vpn/amneziawg-windows-client/releases) - AmneziaWG Official Client
|
||||
|
||||
Linux:
|
||||
|
||||
- [Amnezia VPN](https://amnezia.org/downloads) - Amnezia VPN Official Client
|
||||
- [amneziawg-tools](https://github.com/amnezia-vpn/amneziawg-tools) - AmneziaWG Tools
|
||||
|
||||
OpenWRT:
|
||||
|
||||
- [AmneziaWG OpenWRT](https://github.com/Slava-Shchipunov/awg-openwrt) - AmneziaWG OpenWRT Packages
|
||||
- [AmneziaWG OpenWRT](https://github.com/lolo6oT/awg-openwrt) - AmneziaWG OpenWRT Packages
|
||||
|
||||
@@ -20,74 +20,3 @@ You will however still see a IPv6 address in the Web UI, but it won't be used.
|
||||
This option can be removed in the future, as more devices support IPv6.
|
||||
|
||||
///
|
||||
|
||||
## Configuration Overrides
|
||||
|
||||
These environment variables allow you to override settings that would normally be configured through the Admin Panel. When set, these values take precedence over database settings at runtime.
|
||||
|
||||
### Interface Settings
|
||||
|
||||
| Env | Example | Description |
|
||||
| -------------- | ------------- | ------------------------- |
|
||||
| `WG_PORT` | `51820` | WireGuard interface port |
|
||||
| `WG_DEVICE` | `eth0` | Network device/interface |
|
||||
| `WG_MTU` | `1420` | Maximum Transmission Unit |
|
||||
| `WG_IPV4_CIDR` | `10.8.0.0/24` | IPv4 CIDR range |
|
||||
| `WG_IPV6_CIDR` | `fdcc::/112` | IPv6 CIDR range |
|
||||
|
||||
### Client Connection Settings
|
||||
|
||||
| Env | Example | Description |
|
||||
| --------------------------------- | ----------------- | ------------------------------- |
|
||||
| `WG_HOST` | `vpn.example.com` | Host clients will connect to |
|
||||
| `WG_CLIENT_PORT` | `51820` | Port clients will connect to |
|
||||
| `WG_DEFAULT_DNS` | `1.1.1.1,8.8.8.8` | Default DNS servers for clients |
|
||||
| `WG_DEFAULT_ALLOWED_IPS` | `0.0.0.0/0,::/0` | Default allowed IPs for clients |
|
||||
| `WG_DEFAULT_MTU` | `1420` | Default MTU for clients |
|
||||
| `WG_DEFAULT_PERSISTENT_KEEPALIVE` | `25` | Default persistent keepalive |
|
||||
|
||||
### General Settings
|
||||
|
||||
| Env | Example | Description |
|
||||
| ----------------------- | ----------------- | ------------------------- |
|
||||
| `WG_SESSION_TIMEOUT` | `3600` | Session timeout (seconds) |
|
||||
| `WG_METRICS_PASSWORD` | `mypassword123` | Metrics endpoint password |
|
||||
| `WG_METRICS_PROMETHEUS` | `true` or `false` | Enable Prometheus metrics |
|
||||
| `WG_METRICS_JSON` | `true` or `false` | Enable JSON metrics |
|
||||
|
||||
### Hooks
|
||||
|
||||
| Env | Example | Description |
|
||||
| -------------- | ------------------------- | --------------------- |
|
||||
| `WG_PRE_UP` | `echo "Starting WG"` | PreUp hook command |
|
||||
| `WG_POST_UP` | `iptables -A FORWARD ...` | PostUp hook command |
|
||||
| `WG_PRE_DOWN` | `echo "Stopping WG"` | PreDown hook command |
|
||||
| `WG_POST_DOWN` | `iptables -D FORWARD ...` | PostDown hook command |
|
||||
|
||||
/// warning | Override Behavior
|
||||
|
||||
When these override environment variables are set:
|
||||
|
||||
- The specified values will be used at runtime instead of database settings
|
||||
- You can still update these fields through the Web UI and they will be saved to the database
|
||||
- However, the overridden values from environment variables will always take precedence at runtime
|
||||
- The Web UI will display the database values with warning indicators showing which fields are overridden
|
||||
- On first start, if no database values exist, some overridden values will be saved to the database
|
||||
|
||||
Some overrides will not be applied to existing clients until they are manually edited.
|
||||
|
||||
- `WG_DEFAULT_*` settings will only apply to new clients
|
||||
- `WG_IPV4_CIDR` and `WG_IPV6_CIDR` changes will require clients to be manually edited to take effect
|
||||
|
||||
///
|
||||
|
||||
/// note | Note on Port Variables
|
||||
|
||||
- `WG_PORT` - The port WireGuard listens on (interface port)
|
||||
- `WG_CLIENT_PORT` - The port clients connect to (endpoint port, uses `WG_PORT` if not set)
|
||||
- `PORT` - The port the Web UI listens on (HTTP server port)
|
||||
|
||||
In most cases you will only need to set `WG_PORT` to change the WireGuard port.
|
||||
Keep in mind that you have to adjust both sides of the port publish option in your docker setup.
|
||||
|
||||
///
|
||||
|
||||
@@ -11,20 +11,18 @@ These will only be used during the first start of the container. After that, the
|
||||
| `INIT_ENABLED` | `true` | Enables the below env vars | 0 |
|
||||
| `INIT_USERNAME` | `admin` | Sets admin username | 1 |
|
||||
| `INIT_PASSWORD` | `Se!ureP%ssw` | Sets admin password | 1 |
|
||||
| `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 2 |
|
||||
| `INIT_PORT` | `51820` | Port clients will connect to and WireGuard will listen on | 2 |
|
||||
| `INIT_DNS` | `1.1.1.1,8.8.8.8` | Sets global dns setting | 3 |
|
||||
| `INIT_IPV4_CIDR` | `10.8.0.0/24` | Sets IPv4 cidr | 4 |
|
||||
| `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 4 |
|
||||
| `INIT_ALLOWED_IPS` | `10.8.0.0/24,2001:0DB8::/32` | Sets global Allowed IPs | 5 |
|
||||
| `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 1 |
|
||||
| `INIT_PORT` | `51820` | Port clients will connect to and wireguard will listen on | 1 |
|
||||
| `INIT_DNS` | `1.1.1.1,8.8.8.8` | Sets global dns setting | 2 |
|
||||
| `INIT_IPV4_CIDR` | `10.8.0.0/24` | Sets IPv4 cidr | 3 |
|
||||
| `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 3 |
|
||||
| `INIT_ALLOWED_IPS` | `10.8.0.0/24,2001:0DB8::/32` | Sets global Allowed IPs | 4 |
|
||||
|
||||
/// warning | Variables have to be used together
|
||||
|
||||
If variables are in the same group, you have to set all of them. For example, if you set `INIT_IPV4_CIDR`, you also have to set `INIT_IPV6_CIDR`.
|
||||
|
||||
To skip the setup process, you must configure groups `1` and `2`. You can alternatively use `WG_HOST` and `WG_PORT` to set group `2` without using the `INIT_` variables.
|
||||
|
||||
Avoid setting both `INIT_` and `WG_` variables for the same setting to prevent confusion.
|
||||
If you want to skip the setup process, you have to configure group `1`
|
||||
///
|
||||
|
||||
/// note | Security
|
||||
|
||||
@@ -7,7 +7,7 @@ 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.
|
||||
- If you use armv6, you unfortunately won't be able to migrate to `v15`.
|
||||
- If you use armv6 or armv7, you unfortunately won't be able to migrate to `v15`.
|
||||
- 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
|
||||
@@ -51,9 +51,7 @@ In the setup wizard, select that you already have a configuration file and uploa
|
||||
|
||||
### Environment Variables
|
||||
|
||||
v15 does use some of the environment variables as v14. View [Configuration Overrides](../config/optional-config.md#configuration-overrides) to see which environment variables are supported in v15.
|
||||
|
||||
If you want to be able to change settings through the Web UI, do not set the corresponding environment variables, as they will override the database settings. Instead, manually change the settings through the Web UI after the migration.
|
||||
v15 does not use the same environment variables as v14, most of them have been moved to the Admin Panel in the Web UI.
|
||||
|
||||
### Done
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ title: Basic Installation
|
||||
|
||||
1. You need to have a host that you can manage
|
||||
2. You need to have a domain name or a public IP address
|
||||
3. You need a supported architecture (x86_64, arm64, armv7)
|
||||
3. You need a supported architecture (x86_64, arm64)
|
||||
4. You need curl installed on your host
|
||||
|
||||
## Install Docker
|
||||
|
||||
@@ -93,3 +93,19 @@ PostDown
|
||||
```shell
|
||||
iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
/// warning | Important: When using nftables use the following hooks instead.
|
||||
|
||||
PostUp
|
||||
|
||||
```shell
|
||||
nft add chain ip filter WG_EASY; nft add rule ip filter DOCKER-USER jump WG_EASY; nft add rule ip filter WG_EASY iifname {{device}} accept; nft add rule ip filter WG_EASY oifname {{device}} accept; nft add chain ip6 filter WG_EASY; nft add rule ip6 filter DOCKER-USER jump WG_EASY; nft add rule ip6 filter WG_EASY iifname {{device}} accept; nft add rule ip6 filter WG_EASY oifname {{device}} accept;
|
||||
```
|
||||
|
||||
PostDown
|
||||
|
||||
```shell
|
||||
nft delete rule ip filter DOCKER-USER handle $(nft -a list chain ip filter DOCKER-USER | awk '/jump WG_EASY/ {print $NF}'); nft flush chain ip filter WG_EASY; nft delete chain ip filter WG_EASY; nft delete rule ip6 filter DOCKER-USER handle $(nft -a list chain ip6 filter DOCKER-USER | awk '/jump WG_EASY/ {print $NF}'); nft flush chain ip6 filter WG_EASY; nft delete chain ip6 filter WG_EASY
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
@@ -12,7 +12,7 @@ Before you can get started with deploying your own VPN, there are some requireme
|
||||
|
||||
1. You need to have a host that you can manage
|
||||
2. You need to have a domain name or a public IP address
|
||||
3. You need a supported architecture (x86_64, arm64, armv7)
|
||||
3. You need a supported architecture (x86_64, arm64)
|
||||
|
||||
### Host Setup
|
||||
|
||||
|
||||
+3
-2
@@ -7,10 +7,11 @@
|
||||
"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",
|
||||
"scripts:version": "bash scripts/version.sh",
|
||||
"scripts:i18n": "bash scripts/i18n.sh",
|
||||
"format:check:docs": "prettier --check docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.6.2"
|
||||
"prettier": "^3.7.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.21.0"
|
||||
"packageManager": "pnpm@10.28.0"
|
||||
}
|
||||
|
||||
Generated
+5
-5
@@ -9,16 +9,16 @@ importers:
|
||||
.:
|
||||
devDependencies:
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2
|
||||
specifier: ^3.7.4
|
||||
version: 3.7.4
|
||||
|
||||
packages:
|
||||
|
||||
prettier@3.6.2:
|
||||
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||
prettier@3.7.4:
|
||||
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
prettier@3.6.2: {}
|
||||
prettier@3.7.4: {}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="overflow-x-auto rounded border-2 border-red-800 py-2">
|
||||
<pre
|
||||
class="mx-2 inline-block"
|
||||
@click="selectCode"
|
||||
><code ref="codeBlock">{{ code }}</code></pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
code: string;
|
||||
}>();
|
||||
|
||||
const codeBlock = useTemplateRef('codeBlock');
|
||||
|
||||
function selectCode() {
|
||||
// TODO: keyboard support?
|
||||
if (codeBlock.value) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(codeBlock.value);
|
||||
const sel = window.getSelection();
|
||||
if (sel) {
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<BaseDialog :trigger-class="triggerClass">
|
||||
<template #trigger>
|
||||
<slot />
|
||||
</template>
|
||||
<template #title>
|
||||
{{ $t('client.config') }}
|
||||
</template>
|
||||
<template #description>
|
||||
<div v-if="status === 'success'">
|
||||
<BaseCodeBlock :code="config ?? ''" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>{{ $t('general.loading') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose as-child>
|
||||
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
<DialogClose as-child>
|
||||
<BasePrimaryButton @click="copyCode">
|
||||
{{ $t('copy.copy') }}
|
||||
</BasePrimaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ triggerClass?: string; clientId: number }>();
|
||||
|
||||
const toast = useToast();
|
||||
const { copied, copy, isSupported } = useClipboard({
|
||||
// fallback does not work
|
||||
legacy: false,
|
||||
});
|
||||
|
||||
const { data: config, status } = useFetch(
|
||||
`/api/client/${props.clientId}/configuration`,
|
||||
{
|
||||
responseType: 'text',
|
||||
server: false,
|
||||
}
|
||||
);
|
||||
|
||||
async function copyCode() {
|
||||
if (status.value !== 'success') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSupported.value) {
|
||||
toast.showToast({
|
||||
type: 'error',
|
||||
message: $t('copy.notSupported'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await copy(config.value ?? '');
|
||||
|
||||
if (copied.value) {
|
||||
toast.showToast({
|
||||
type: 'success',
|
||||
message: $t('copy.copied'),
|
||||
});
|
||||
} else {
|
||||
toast.showToast({
|
||||
type: 'error',
|
||||
message: $t('copy.failed'),
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose>
|
||||
<DialogClose as-child>
|
||||
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div
|
||||
v-if="overridden"
|
||||
class="flex w-fit items-center gap-2 rounded-lg bg-amber-50 p-2 text-sm text-amber-700 dark:bg-amber-900/20 dark:text-amber-400"
|
||||
>
|
||||
<IconsWarning class="size-4" />
|
||||
<span>This field is overridden by an environment variable</span>
|
||||
</div>
|
||||
<div v-if="data?.length === 0">
|
||||
{{ emptyText || $t('form.noItems') }}
|
||||
</div>
|
||||
@@ -42,11 +35,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
const data = defineModel<string[]>();
|
||||
defineProps<{
|
||||
emptyText?: string[];
|
||||
name: string;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
defineProps<{ emptyText?: string[]; name: string }>();
|
||||
|
||||
function update(e: Event, i: number) {
|
||||
const v = (e.target as HTMLInputElement).value;
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
<BaseTooltip
|
||||
v-if="overridden"
|
||||
text="This field is overridden by an environment variable"
|
||||
>
|
||||
<IconsWarning class="size-4 text-amber-500" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<BaseInput
|
||||
@@ -44,7 +38,6 @@ defineProps<{
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
url: '/api/admin/ip-info' | '/api/setup/4';
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
|
||||
const data = defineModel<string | null>({
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
<BaseTooltip
|
||||
v-if="overridden"
|
||||
text="This field is overridden by an environment variable"
|
||||
>
|
||||
<IconsWarning class="size-4 text-amber-500" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<BaseInput
|
||||
:id="id"
|
||||
@@ -30,7 +24,6 @@ defineProps<{
|
||||
description?: string;
|
||||
autocomplete?: string;
|
||||
placeholder?: string;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
|
||||
const data = defineModel<string | null>({
|
||||
|
||||
@@ -6,23 +6,12 @@
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
<BaseTooltip
|
||||
v-if="overridden"
|
||||
text="This field is overridden by an environment variable"
|
||||
>
|
||||
<IconsWarning class="size-4 text-amber-500" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<BaseInput :id="id" v-model.number="data" :name="id" type="number" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
defineProps<{ id: string; label: string; description?: string }>();
|
||||
|
||||
const data = defineModel<number>();
|
||||
</script>
|
||||
|
||||
@@ -6,22 +6,11 @@
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
<BaseTooltip
|
||||
v-if="overridden"
|
||||
text="This field is overridden by an environment variable"
|
||||
>
|
||||
<IconsWarning class="size-4 text-amber-500" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<BaseSwitch :id="id" v-model="data" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
defineProps<{ id: string; label: string; description?: string }>();
|
||||
const data = defineModel<boolean>();
|
||||
</script>
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
<BaseTooltip
|
||||
v-if="overridden"
|
||||
text="This field is overridden by an environment variable"
|
||||
>
|
||||
<IconsWarning class="size-4 text-amber-500" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<BaseInput
|
||||
:id="id"
|
||||
@@ -30,7 +24,6 @@ defineProps<{
|
||||
description?: string;
|
||||
autocomplete?: string;
|
||||
disabled?: boolean;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
|
||||
const data = defineModel<string>();
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
v-for="(item, index) in menuItems"
|
||||
:key="index"
|
||||
:to="`/admin/${item.id}`"
|
||||
active-class="bg-red-800 rounded"
|
||||
class="group rounded"
|
||||
active-class="bg-red-800 active"
|
||||
>
|
||||
<BaseSecondaryButton
|
||||
as="span"
|
||||
class="w-full cursor-pointer rounded p-2 font-medium transition-colors duration-200 hover:bg-red-800 dark:text-neutral-200"
|
||||
class="w-full font-medium group-[.active]:text-white"
|
||||
>
|
||||
{{ item.name }}
|
||||
</BaseSecondaryButton>
|
||||
|
||||
@@ -9,14 +9,12 @@
|
||||
:label="$t('general.host')"
|
||||
:description="$t('admin.config.hostDesc')"
|
||||
url="/api/admin/ip-info"
|
||||
:overridden="overrides?.host"
|
||||
/>
|
||||
<FormNumberField
|
||||
id="port"
|
||||
v-model="data.port"
|
||||
:label="$t('general.port')"
|
||||
:description="$t('admin.config.portDesc')"
|
||||
:overridden="overrides?.port"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
@@ -26,18 +24,13 @@
|
||||
<FormArrayField
|
||||
v-model="data.defaultAllowedIps"
|
||||
name="defaultAllowedIps"
|
||||
:overridden="overrides?.defaultAllowedIps"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading :description="$t('admin.config.dnsDesc')">
|
||||
{{ $t('general.dns') }}
|
||||
</FormHeading>
|
||||
<FormArrayField
|
||||
v-model="data.defaultDns"
|
||||
name="defaultDns"
|
||||
:overridden="overrides?.defaultDns"
|
||||
/>
|
||||
<FormArrayField v-model="data.defaultDns" name="defaultDns" />
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading>
|
||||
@@ -46,14 +39,12 @@
|
||||
v-model="data.defaultMtu"
|
||||
:label="$t('general.mtu')"
|
||||
:description="$t('admin.config.mtuDesc')"
|
||||
:overridden="overrides?.defaultMtu"
|
||||
/>
|
||||
<FormNumberField
|
||||
id="defaultPersistentKeepalive"
|
||||
v-model="data.defaultPersistentKeepalive"
|
||||
:label="$t('general.persistentKeepalive')"
|
||||
:description="$t('admin.config.persistentKeepaliveDesc')"
|
||||
:overridden="overrides?.defaultPersistentKeepalive"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup v-if="globalStore.information?.isAwg">
|
||||
@@ -127,12 +118,6 @@ const { data: _data, refresh } = await useFetch(`/api/admin/userconfig`, {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
const { data: overridesData } = await useFetch(`/api/admin/overrides`, {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
const overrides = computed(() => overridesData.value?.userConfig);
|
||||
|
||||
const data = toRef(_data.value);
|
||||
|
||||
const _submit = useSubmit(
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
v-model="data.sessionTimeout"
|
||||
:label="$t('admin.general.sessionTimeout')"
|
||||
:description="$t('admin.general.sessionTimeoutDesc')"
|
||||
:overridden="overrides?.sessionTimeout"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
@@ -17,21 +16,18 @@
|
||||
v-model="data.metricsPassword"
|
||||
:label="$t('admin.general.metricsPassword')"
|
||||
:description="$t('admin.general.metricsPasswordDesc')"
|
||||
:overridden="overrides?.metricsPassword"
|
||||
/>
|
||||
<FormSwitchField
|
||||
id="prometheus"
|
||||
v-model="data.metricsPrometheus"
|
||||
:label="$t('admin.general.prometheus')"
|
||||
:description="$t('admin.general.prometheusDesc')"
|
||||
:overridden="overrides?.metricsPrometheus"
|
||||
/>
|
||||
<FormSwitchField
|
||||
id="json"
|
||||
v-model="data.metricsJson"
|
||||
:label="$t('admin.general.json')"
|
||||
:description="$t('admin.general.jsonDesc')"
|
||||
:overridden="overrides?.metricsJson"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
@@ -47,13 +43,6 @@
|
||||
const { data: _data, refresh } = await useFetch(`/api/admin/general`, {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
const { data: overridesData } = await useFetch(`/api/admin/overrides`, {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
const overrides = computed(() => overridesData.value?.general);
|
||||
|
||||
const data = toRef(_data.value);
|
||||
|
||||
const _submit = useSubmit(
|
||||
|
||||
@@ -6,25 +6,21 @@
|
||||
id="PreUp"
|
||||
v-model="data.preUp"
|
||||
:label="$t('hooks.preUp')"
|
||||
:overridden="overrides?.preUp"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PostUp"
|
||||
v-model="data.postUp"
|
||||
:label="$t('hooks.postUp')"
|
||||
:overridden="overrides?.postUp"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PreDown"
|
||||
v-model="data.preDown"
|
||||
:label="$t('hooks.preDown')"
|
||||
:overridden="overrides?.preDown"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PostDown"
|
||||
v-model="data.postDown"
|
||||
:label="$t('hooks.postDown')"
|
||||
:overridden="overrides?.postDown"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
@@ -41,12 +37,6 @@ const { data: _data, refresh } = await useFetch(`/api/admin/hooks`, {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
const { data: overridesData } = await useFetch(`/api/admin/overrides`, {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
const overrides = computed(() => overridesData.value?.hooks);
|
||||
|
||||
const data = toRef(_data.value);
|
||||
|
||||
const _submit = useSubmit(
|
||||
|
||||
@@ -7,21 +7,18 @@
|
||||
v-model="data.mtu"
|
||||
:label="$t('general.mtu')"
|
||||
:description="$t('admin.interface.mtuDesc')"
|
||||
:overridden="overrides?.mtu"
|
||||
/>
|
||||
<FormNumberField
|
||||
id="port"
|
||||
v-model="data.port"
|
||||
:label="$t('general.port')"
|
||||
:description="$t('admin.interface.portDesc')"
|
||||
:overridden="overrides?.port"
|
||||
/>
|
||||
<FormTextField
|
||||
id="device"
|
||||
v-model="data.device"
|
||||
:label="$t('admin.interface.device')"
|
||||
:description="$t('admin.interface.deviceDesc')"
|
||||
:overridden="overrides?.device"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup v-if="globalStore.information?.isAwg">
|
||||
@@ -167,12 +164,6 @@ const { data: _data, refresh } = await useFetch(`/api/admin/interface`, {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
const { data: overridesData } = await useFetch(`/api/admin/overrides`, {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
const overrides = computed(() => overridesData.value?.interface);
|
||||
|
||||
const data = toRef(_data.value);
|
||||
|
||||
const _submit = useSubmit(
|
||||
|
||||
@@ -179,13 +179,25 @@
|
||||
@delete="deleteClient"
|
||||
>
|
||||
<FormSecondaryActionField
|
||||
label="Delete"
|
||||
:label="$t('client.delete')"
|
||||
class="w-full"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
as="span"
|
||||
/>
|
||||
</ClientsDeleteDialog>
|
||||
<ClientsConfigDialog
|
||||
trigger-class="col-span-2"
|
||||
:client-id="data.id"
|
||||
>
|
||||
<FormSecondaryActionField
|
||||
:label="$t('client.viewConfig')"
|
||||
class="w-full"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
as="span"
|
||||
/>
|
||||
</ClientsConfigDialog>
|
||||
</FormGroup>
|
||||
</FormElement>
|
||||
</PanelBody>
|
||||
|
||||
@@ -55,15 +55,9 @@ const _submit = useSubmit(
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
revert: async (success, data) => {
|
||||
revert: async (success) => {
|
||||
if (success) {
|
||||
if (data?.setupDone) {
|
||||
// Setup is complete, redirect to success page
|
||||
await navigateTo('/setup/success');
|
||||
} else {
|
||||
// Continue to step 3
|
||||
await navigateTo('/setup/3');
|
||||
}
|
||||
await navigateTo('/setup/3');
|
||||
}
|
||||
},
|
||||
noSuccessToast: true,
|
||||
|
||||
@@ -7,6 +7,7 @@ import it from './locales/it.json';
|
||||
import ru from './locales/ru.json';
|
||||
import zhhk from './locales/zh-HK.json';
|
||||
import zhcn from './locales/zh-CN.json';
|
||||
import zhtw from './locales/zh-TW.json';
|
||||
import ko from './locales/ko.json';
|
||||
import es from './locales/es.json';
|
||||
import ptbr from './locales/pt-BR.json';
|
||||
@@ -27,6 +28,7 @@ export default defineI18nConfig(() => ({
|
||||
ru,
|
||||
'zh-HK': zhhk,
|
||||
'zh-CN': zhcn,
|
||||
'zh-TW': zhtw,
|
||||
ko,
|
||||
es,
|
||||
'pt-BR': ptbr,
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"name": "Name",
|
||||
"expireDate": "Expire Date",
|
||||
"expireDateDesc": "Date the client will be disabled. Blank for permanent",
|
||||
"delete": "Delete",
|
||||
"deleteClient": "Delete Client",
|
||||
"deleteDialog1": "Are you sure you want to delete",
|
||||
"deleteDialog2": "This action cannot be undone.",
|
||||
@@ -117,7 +118,9 @@
|
||||
"notConnected": "Client not connected",
|
||||
"endpoint": "Endpoint",
|
||||
"endpointDesc": "IP of the client from which the WireGuard connection is established",
|
||||
"search": "Search clients..."
|
||||
"search": "Search clients...",
|
||||
"config": "Configuration",
|
||||
"viewConfig": "View Configuration"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Change",
|
||||
@@ -238,6 +241,12 @@
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
},
|
||||
"copy": {
|
||||
"notSupported": "Copy is not supported",
|
||||
"copied": "Copied!",
|
||||
"failed": "Copy failed",
|
||||
"copy": "Copy"
|
||||
},
|
||||
"awg": {
|
||||
"jCLabel": "Junk packet count (Jc)",
|
||||
"jCDescription": "Number of junk packets to send (1-128, recommended: 4-12)",
|
||||
|
||||
+59
-13
@@ -15,12 +15,12 @@
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "Mot de passe actuel",
|
||||
"enable2fa": "Activer l'authentification à double facteur",
|
||||
"enable2fa": "Activer l'authentification à deux facteurs",
|
||||
"enable2faDesc": "Scannez le code QR avec votre application d'authentification ou saisissez la clé manuellement.",
|
||||
"2faKey": "Clé TOTP",
|
||||
"2faCodeDesc": "Saisissez le code de votre application d'authentification.",
|
||||
"disable2fa": "Désactiver l'authentification à double facteur",
|
||||
"disable2faDesc": "Saisissez votre mot de passe pour désactiver l'authentification à double facteur"
|
||||
"disable2fa": "Désactiver l'authentification à deux facteurs",
|
||||
"disable2faDesc": "Saisissez votre mot de passe pour désactiver l'authentification à deux facteurs."
|
||||
},
|
||||
"general": {
|
||||
"name": "Nom",
|
||||
@@ -40,7 +40,7 @@
|
||||
"no": "Non",
|
||||
"confirmPassword": "Confirmer le mot de passe",
|
||||
"loading": "Chargement...",
|
||||
"2fa": "Authentification à double facteur",
|
||||
"2fa": "Authentification à deux facteurs",
|
||||
"2faCode": "Code TOTP"
|
||||
},
|
||||
"setup": {
|
||||
@@ -75,8 +75,8 @@
|
||||
"rememberMe": "Se souvenir de moi",
|
||||
"rememberMeDesc": "Rester connecté après avoir fermé le navigateur",
|
||||
"insecure": "Vous ne pouvez pas vous connecter avec une connexion non sécurisée. Utilisez HTTPS.",
|
||||
"2faRequired": "Une authentification à double facteur est requise",
|
||||
"2faWrong": "L'authentification à double facteur est incorrecte"
|
||||
"2faRequired": "L'authentification à deux facteurs est requise",
|
||||
"2faWrong": "Le code d'authentification à deux facteurs est incorrect"
|
||||
},
|
||||
"client": {
|
||||
"empty": "Il n'y a pas encore de clients.",
|
||||
@@ -108,7 +108,7 @@
|
||||
"downloadConfig": "Télécharger la configuration",
|
||||
"allowedIpsDesc": "Quelles IPs seront acheminées par le VPN (remplace la configuration globale)",
|
||||
"serverAllowedIpsDesc": "Les IPs que le serveur acheminera vers le client",
|
||||
"mtuDesc": "Définit le nombre maximum d'unités de transmission (taille des paquets) pour le tunnel VPN.",
|
||||
"mtuDesc": "Définit l'unité de transmission maximale (taille des paquets) pour le tunnel VPN",
|
||||
"persistentKeepaliveDesc": "Définit l'intervalle (en secondes) pour les paquets keep-alive. 0 le désactive",
|
||||
"hooks": "Hooks",
|
||||
"hooksDescription": "Les hooks ne fonctionnent qu'avec wg-quick",
|
||||
@@ -116,7 +116,11 @@
|
||||
"dnsDesc": "Serveur DNS que les clients utiliseront (remplace la configuration globale)",
|
||||
"notConnected": "Client non connecté",
|
||||
"endpoint": "Endpoint",
|
||||
"endpointDesc": "Adresse IP du client à partir duquel la connexion WireGuard est établie"
|
||||
"endpointDesc": "Adresse IP du client à partir duquel la connexion WireGuard est établie",
|
||||
"search": "Rechercher des clients...",
|
||||
"config": "Configuration",
|
||||
"viewConfig": "Voir la configuration",
|
||||
"delete": "Supprimer"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Modifier",
|
||||
@@ -146,9 +150,9 @@
|
||||
"metricsPassword": "Mot de passe",
|
||||
"metricsPasswordDesc": "Mot de passe Bearer pour le endpoint des métriques (mot de passe ou argon2 hash)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "Acheminement pour les métriques au format JSON",
|
||||
"jsonDesc": "Route pour les métriques au format JSON",
|
||||
"prometheus": "Prometheus",
|
||||
"prometheusDesc": "Acheminement pour les métriques de Prometheus"
|
||||
"prometheusDesc": "Route pour les métriques Prometheus"
|
||||
},
|
||||
"config": {
|
||||
"connection": "Connexion",
|
||||
@@ -180,13 +184,13 @@
|
||||
"required": "{0} est requis",
|
||||
"validNumber": "{0} doit être un nombre valide",
|
||||
"validString": "{0} doit être une chaîne de caractères valide",
|
||||
"validBoolean": "{0} doit être une variable valide",
|
||||
"validBoolean": "{0} doit être un booléen valide",
|
||||
"validArray": "{0} doit être un tableau valide",
|
||||
"stringMin": "{0} doit être d'au moins {1} Caractère",
|
||||
"stringMin": "{0} doit comporter au moins {1} caractère(s)",
|
||||
"numberMin": "{0} doit être d'au moins {1}"
|
||||
},
|
||||
"client": {
|
||||
"id": "Client ID",
|
||||
"id": "ID du client",
|
||||
"name": "Nom",
|
||||
"expiresAt": "Expire le",
|
||||
"address4": "Adresse IPv4",
|
||||
@@ -236,5 +240,47 @@
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
},
|
||||
"copy": {
|
||||
"notSupported": "La copie n'est pas prise en charge",
|
||||
"copied": "Copié !",
|
||||
"failed": "Échec de la copie",
|
||||
"copy": "Copier"
|
||||
},
|
||||
"awg": {
|
||||
"jCLabel": "Nombre de paquets parasites (Jc)",
|
||||
"jCDescription": "Nombre de paquets parasites à envoyer (1-128, recommandé : 4-12)",
|
||||
"jMinLabel": "Taille min des paquets parasites (Jmin)",
|
||||
"jMinDescription": "Taille minimale des paquets parasites (0-1279*, recommandé : 8, doit être < Jmax)",
|
||||
"jMaxLabel": "Taille max des paquets parasites (Jmax)",
|
||||
"jMaxDescription": "Taille maximale des paquets parasites (1-1280*, recommandé : 80, doit être > Jmin)",
|
||||
"s1Label": "Taille parasite du paquet init (S1)",
|
||||
"s1Description": "Taille parasite du paquet d'initialisation (0-1132[1280* - 148 = 1132], recommandé : 15-150, S1+56 ≠ S2)",
|
||||
"s2Label": "Taille parasite du paquet réponse (S2)",
|
||||
"s2Description": "Taille parasite du paquet de réponse (0-1188[1280* - 92 = 1188], recommandé : 15-150)",
|
||||
"s3Label": "Taille parasite du paquet cookie reply (S3)",
|
||||
"s3Description": "Taille parasite du paquet de réponse cookie",
|
||||
"s4Label": "Taille parasite du paquet transport (S4)",
|
||||
"s4Description": "Taille parasite du paquet de transport",
|
||||
"i1Label": "Paquet parasite spécial 1 (I1)",
|
||||
"i1Description": "Paquet de simulation de protocole en format hexadécimal : <b 0x...>",
|
||||
"i2Label": "Paquet parasite spécial 2 (I2)",
|
||||
"i2Description": "Paquet de simulation de protocole en format hexadécimal : <b 0x...>",
|
||||
"i3Label": "Paquet parasite spécial 3 (I3)",
|
||||
"i3Description": "Paquet de simulation de protocole en format hexadécimal : <b 0x...>",
|
||||
"i4Label": "Paquet parasite spécial 4 (I4)",
|
||||
"i4Description": "Paquet de simulation de protocole en format hexadécimal : <b 0x...>",
|
||||
"i5Label": "Paquet parasite spécial 5 (I5)",
|
||||
"i5Description": "Paquet de simulation de protocole en format hexadécimal : <b 0x...>",
|
||||
"h1Label": "En-tête magique init (H1)",
|
||||
"h1Description": "Valeur d'en-tête du paquet init (5-2147483647, doit être unique par rapport à H2-H4)",
|
||||
"h2Label": "En-tête magique réponse (H2)",
|
||||
"h2Description": "Valeur d'en-tête du paquet réponse (5-2147483647, doit être unique par rapport à H1, H3, H4)",
|
||||
"h3Label": "En-tête magique cookie reply (H3)",
|
||||
"h3Description": "Valeur d'en-tête du paquet cookie reply (5-2147483647, doit être unique par rapport à H1, H2, H4)",
|
||||
"h4Label": "En-tête magique transport (H4)",
|
||||
"h4Description": "Valeur d'en-tête du paquet transport (5-2147483647, doit être unique par rapport à H1-H3)",
|
||||
"mtuNote": "Les valeurs dépendent du MTU",
|
||||
"obfuscationParameters": "Paramètres d'obfuscation AmneziaWG"
|
||||
}
|
||||
}
|
||||
|
||||
+117
-71
@@ -3,8 +3,8 @@
|
||||
"me": "Аккаунт",
|
||||
"clients": "Клиенты",
|
||||
"admin": {
|
||||
"panel": "Админ панель",
|
||||
"general": "Общие",
|
||||
"panel": "Админ-панель",
|
||||
"general": "Общие настройки",
|
||||
"config": "Конфигурация",
|
||||
"interface": "Интерфейс",
|
||||
"hooks": "Хуки"
|
||||
@@ -16,11 +16,11 @@
|
||||
"me": {
|
||||
"currentPassword": "Текущий пароль",
|
||||
"enable2fa": "Включить двухфакторную аутентификацию",
|
||||
"enable2faDesc": "Отсканируйте QR-код приложением-аутентификатором или введите ключ вручную.",
|
||||
"2faKey": "TOTP-ключ",
|
||||
"2faCodeDesc": "Введите код из приложения-аутентификатора.",
|
||||
"enable2faDesc": "Отсканируйте QR‑код с помощью приложения‑аутентификатора или введите ключ вручную.",
|
||||
"2faKey": "Ключ TOTP",
|
||||
"2faCodeDesc": "Введите код из приложения‑аутентификатора.",
|
||||
"disable2fa": "Отключить двухфакторную аутентификацию",
|
||||
"disable2faDesc": "Введите пароль, чтобы отключить двухфакторную аутентификацию"
|
||||
"disable2faDesc": "Введите пароль, чтобы отключить двухфакторную аутентификацию."
|
||||
},
|
||||
"general": {
|
||||
"name": "Имя",
|
||||
@@ -29,9 +29,9 @@
|
||||
"newPassword": "Новый пароль",
|
||||
"updatePassword": "Обновить пароль",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "Разрешённые IP",
|
||||
"allowedIps": "Разрешённые IP‑адреса",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "Постоянный keepalive",
|
||||
"persistentKeepalive": "Постоянное поддержание соединения",
|
||||
"logout": "Выйти",
|
||||
"continue": "Продолжить",
|
||||
"host": "Хост",
|
||||
@@ -41,21 +41,21 @@
|
||||
"confirmPassword": "Подтвердите пароль",
|
||||
"loading": "Загрузка...",
|
||||
"2fa": "Двухфакторная аутентификация",
|
||||
"2faCode": "TOTP‑код"
|
||||
"2faCode": "Код TOTP"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "Добро пожаловать в первичную настройку wg-easy",
|
||||
"welcomeDesc": "Вы нашли самый простой способ установить и управлять WireGuard на любом Linux-хосте",
|
||||
"existingSetup": "У вас уже есть существующая установка?",
|
||||
"createAdminDesc": "Сначала введите имя администратора и надёжный пароль. Эти данные понадобятся для входа в панель управления",
|
||||
"setupConfigDesc": "Введите информацию о хосте и порте. Она будет использоваться в конфигурации клиента при установке WireGuard на устройствах",
|
||||
"setupMigrationDesc": "Укажите файл резервной копии, если хотите перенести данные из предыдущей версии wg-easy",
|
||||
"welcomeDesc": "Вы нашли самый простой способ установить и управлять WireGuard на любом Linux‑хосте",
|
||||
"existingSetup": "У вас уже есть существующая настройка?",
|
||||
"createAdminDesc": "Сначала введите имя администратора и надёжный пароль. Эти данные будут использоваться для входа в Админ-панель.",
|
||||
"setupConfigDesc": "Введите данные хоста и порта. Они будут использоваться для настройки клиента при установке WireGuard на устройствах.",
|
||||
"setupMigrationDesc": "Укажите файл резервной копии, если хотите перенести данные из предыдущей версии wg-easy.",
|
||||
"upload": "Загрузить",
|
||||
"migration": "Восстановить из резервной копии:",
|
||||
"createAccount": "Создать аккаунт",
|
||||
"successful": "Настройка успешна",
|
||||
"successful": "Настройка завершена успешно",
|
||||
"hostDesc": "Публичное имя хоста, к которому будут подключаться клиенты",
|
||||
"portDesc": "Публичный UDP‑порт для подключения клиентов и прослушивания WireGuard"
|
||||
"portDesc": "Публичный UDP‑порт, к которому будут подключаться клиенты и на котором будет слушать WireGuard"
|
||||
},
|
||||
"update": {
|
||||
"updateAvailable": "Доступно обновление!",
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
"layout": {
|
||||
"toggleCharts": "Показать/скрыть графики",
|
||||
"donate": "Пожертвовать"
|
||||
"donate": "Поддержать"
|
||||
},
|
||||
"login": {
|
||||
"signIn": "Войти",
|
||||
@@ -87,18 +87,19 @@
|
||||
"new": "Новый клиент",
|
||||
"name": "Имя",
|
||||
"expireDate": "Дата отключения",
|
||||
"expireDateDesc": "Дата, когда клиент будет отключён. Пусто — бессрочно",
|
||||
"expireDateDesc": "Дата, когда клиент будет отключён. Оставьте пустым для бессрочного доступа",
|
||||
"delete": "Удалить",
|
||||
"deleteClient": "Удалить клиента",
|
||||
"deleteDialog1": "Вы уверены, что хотите удалить",
|
||||
"deleteDialog2": "Это действие необратимо.",
|
||||
"enabled": "Включен",
|
||||
"deleteDialog2": "Это действие нельзя отменить.",
|
||||
"enabled": "Включён",
|
||||
"address": "Адрес",
|
||||
"serverAllowedIps": "Разрешённые IP сервера",
|
||||
"otlDesc": "Сгенерировать одноразовую короткую ссылку",
|
||||
"permanent": "Постоянный",
|
||||
"serverAllowedIps": "Разрешённые IP‑адреса сервера",
|
||||
"otlDesc": "Сгенерировать короткую одноразовую ссылку",
|
||||
"permanent": "Бессрочный",
|
||||
"createdOn": "Создан ",
|
||||
"lastSeen": "Последнее подключение ",
|
||||
"totalDownload": "Всего загружено: ",
|
||||
"totalDownload": "Всего скачано: ",
|
||||
"totalUpload": "Всего отправлено: ",
|
||||
"newClient": "Новый клиент",
|
||||
"disableClient": "Отключить клиента",
|
||||
@@ -106,25 +107,28 @@
|
||||
"noPrivKey": "У этого клиента нет приватного ключа. Невозможно создать конфигурацию.",
|
||||
"showQR": "Показать QR‑код",
|
||||
"downloadConfig": "Скачать конфигурацию",
|
||||
"allowedIpsDesc": "Какие IP будут маршрутизироваться через VPN (перезаписывает общую конфигурацию)",
|
||||
"serverAllowedIpsDesc": "Какие IP сервер будет отправлять клиенту",
|
||||
"mtuDesc": "Максимальный размер пакета для VPN‑туннеля",
|
||||
"persistentKeepaliveDesc": "Интервал пакетов для поддержания соединения (в секундах). 0 — отключено.",
|
||||
"allowedIpsDesc": "Какие IP‑адреса будут маршрутизироваться через VPN (переопределяет глобальную конфигурацию)",
|
||||
"serverAllowedIpsDesc": "Какие IP‑адреса сервер будет отправлять клиенту",
|
||||
"mtuDesc": "Максимальный размер пакета (MTU) для VPN‑туннеля",
|
||||
"persistentKeepaliveDesc": "Устанавливает интервал (в секундах) для пакетов поддержания соединения. 0 — отключить",
|
||||
"hooks": "Хуки",
|
||||
"hooksDescription": "Хуки работают только с wg-quick",
|
||||
"hooksLeaveEmpty": "Только для wg-quick. Иначе оставьте пустым",
|
||||
"dnsDesc": "DNS‑сервер, который будут использовать клиенты (перезаписывает общую конфигурацию)",
|
||||
"hooksDescription": "Хуки работают только с wg‑quick",
|
||||
"hooksLeaveEmpty": "Только для wg‑quick. В остальных случаях оставьте пустым",
|
||||
"dnsDesc": "DNS‑сервер, который будут использовать клиенты (переопределяет глобальную конфигурацию)",
|
||||
"notConnected": "Клиент не подключен",
|
||||
"endpoint": "Конечная точка",
|
||||
"endpointDesc": "IP-адрес клиента, с которого установлено соединение WireGuard"
|
||||
"endpoint": "Точка подключения",
|
||||
"endpointDesc": "IP‑адрес клиента, с которого установлено соединение WireGuard",
|
||||
"search": "Поиск клиентов...",
|
||||
"config": "Конфигурация",
|
||||
"viewConfig": "Просмотреть конфигурацию"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Изменить",
|
||||
"cancel": "Отмена",
|
||||
"cancel": "Отменить",
|
||||
"create": "Создать"
|
||||
},
|
||||
"toast": {
|
||||
"success": "Успех",
|
||||
"success": "Успешно",
|
||||
"saved": "Сохранено",
|
||||
"error": "Ошибка"
|
||||
},
|
||||
@@ -133,18 +137,18 @@
|
||||
"save": "Сохранить",
|
||||
"revert": "Отменить",
|
||||
"sectionGeneral": "Общие",
|
||||
"sectionAdvanced": "Дополнительно",
|
||||
"sectionAdvanced": "Расширенные",
|
||||
"noItems": "Нет элементов",
|
||||
"nullNoItems": "Нет элементов. Используется глобальная конфигурация",
|
||||
"add": "Добавить"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "Тайм-аут сессии",
|
||||
"sessionTimeoutDesc": "Длительность сеанса для \"Запомнить меня\" (секунды)",
|
||||
"sessionTimeout": "Время жизни сессии",
|
||||
"sessionTimeoutDesc": "Длительность сессии для «Запомнить меня» (в секундах)",
|
||||
"metrics": "Метрики",
|
||||
"metricsPassword": "Пароль",
|
||||
"metricsPasswordDesc": "Пароль Bearer для эндпоинта метрик (пароль или хеш argon2)",
|
||||
"metricsPasswordDesc": "Пароль Bearer для конечной точки метрик (пароль или хэш argon2)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "Путь для метрик в формате JSON",
|
||||
"prometheus": "Prometheus",
|
||||
@@ -152,83 +156,83 @@
|
||||
},
|
||||
"config": {
|
||||
"connection": "Соединение",
|
||||
"hostDesc": "Публичное имя хоста для подключения клиентов (сбросит конфигурацию)",
|
||||
"portDesc": "Публичный UDP‑порт для подключения клиентов (также стоит изменить порт интерфейса)",
|
||||
"allowedIpsDesc": "Разрешённые IP для клиентов (общая конфигурация)",
|
||||
"dnsDesc": "DNS‑сервер для клиентов (общая конфигурация)",
|
||||
"hostDesc": "Публичное имя хоста для подключения клиентов(обнуляет конфигурацию)",
|
||||
"portDesc": "Публичный UDP‑порт для подключения клиентов (также рекомендуется изменить порт интерфейса)",
|
||||
"allowedIpsDesc": "Разрешённые IP‑адреса для клиентов(глобальная конфигурация)",
|
||||
"dnsDesc": "DNS‑сервер для клиентов (глобальная конфигурация)",
|
||||
"mtuDesc": "MTU для клиентов (только для новых)",
|
||||
"persistentKeepaliveDesc": "Интервал отправки keepalive на сервер (секунды). 0 = отключено (только для новых)",
|
||||
"suggest": "Определить",
|
||||
"suggestDesc": "Выберите IP‑адрес или имя хоста для поля Host"
|
||||
"persistentKeepaliveDesc": "Интервал в секундах для отправки пакетов поддержания соединения на сервер. 0 = отключено (только для новых клиентов)",
|
||||
"suggest": "Предложить",
|
||||
"suggestDesc": "Выберите IP‑адрес или имя хоста для поля «Хост»"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "CIDR изменён",
|
||||
"device": "Устройство",
|
||||
"deviceDesc": "Сетевое устройство, через которое должен проходить трафик WireGuard",
|
||||
"mtuDesc": "MTU, который использует WireGuard",
|
||||
"portDesc": "UDP‑порт, на котором WireGuard будет слушать (возможно, нужно изменить и порт конфигурации)",
|
||||
"deviceDesc": "Сетевое устройство Ethernet, через которое должен проходить трафик WireGuard",
|
||||
"mtuDesc": "MTU, который будет использовать WireGuard",
|
||||
"portDesc": "UDP‑порт, на котором будет слушать WireGuard (возможно, нужно также изменить порт конфигурации)",
|
||||
"changeCidr": "Изменить CIDR",
|
||||
"restart": "Перезапустить интерфейс",
|
||||
"restartDesc": "Перезапустить интерфейс WireGuard",
|
||||
"restartWarn": "Вы уверены, что хотите перезапустить интерфейс? Все клиенты будут отключены.",
|
||||
"restartWarn": "Вы уверены, что хотите перезапустить интерфейс? Это приведёт к отключению всех клиентов.",
|
||||
"restartSuccess": "Интерфейс перезапущен"
|
||||
},
|
||||
"introText": "Добро пожаловать в панель администратора.\n\nЗдесь вы можете управлять общими настройками, конфигурацией, параметрами интерфейса и хуками.\n\nНачните с выбора раздела в боковой панели."
|
||||
"introText": "Добро пожаловать в панель администратора.\n\nЗдесь вы можете управлять общими настройками, конфигурацией, настройками интерфейса и хуками.\n\nНачните с выбора одного из разделов на боковой панели."
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
"required": "{0} обязательное поле",
|
||||
"validNumber": "{0} должен быть числом",
|
||||
"validString": "{0} должна быть строкой",
|
||||
"validBoolean": "{0} должен быть булевым значением",
|
||||
"validArray": "{0} должен быть массивом",
|
||||
"stringMin": "{0} должен содержать не менее {1} символов",
|
||||
"numberMin": "{0} должен быть не меньше {1}"
|
||||
"required": "{0} обязательно для заполнения",
|
||||
"validNumber": "{0} должно быть числом",
|
||||
"validString": "{0} должно быть строкой",
|
||||
"validBoolean": "{0} должно быть логическим значением",
|
||||
"validArray": "{0} должно быть массивом",
|
||||
"stringMin": "{0} должно содержать не менее {1} символа",
|
||||
"numberMin": "{0} должно быть не менее {1}"
|
||||
},
|
||||
"client": {
|
||||
"id": "ID клиента",
|
||||
"name": "Имя",
|
||||
"expiresAt": "Действителен до",
|
||||
"address4": "IPv4 адрес",
|
||||
"address6": "IPv6 адрес",
|
||||
"serverAllowedIps": "Разрешённые IP сервера"
|
||||
"expiresAt": "Дата окончания действия",
|
||||
"address4": "IPv4‑адрес",
|
||||
"address6": "IPv6‑адрес",
|
||||
"serverAllowedIps": "Разрешённые IP‑адреса сервера"
|
||||
},
|
||||
"user": {
|
||||
"username": "Имя пользователя",
|
||||
"password": "Пароль",
|
||||
"remember": "Запомнить",
|
||||
"name": "Имя",
|
||||
"email": "Email",
|
||||
"emailInvalid": "Email должен быть валидным",
|
||||
"email": "Электронная почта",
|
||||
"emailInvalid": "Адрес электронной почты должен быть корректным",
|
||||
"passwordMatch": "Пароли должны совпадать",
|
||||
"totpEnable": "Включить TOTP",
|
||||
"totpEnableTrue": "Необходимо включить TOTP",
|
||||
"totpCode": "TOTP‑код"
|
||||
"totpEnableTrue": "TOTP должен быть включён",
|
||||
"totpCode": "Код TOTP"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "Хост"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "Тайм-аут сессии",
|
||||
"sessionTimeout": "Время жизни сессии",
|
||||
"metricsEnabled": "Метрики",
|
||||
"metricsPassword": "Пароль для метрик"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "Устройство",
|
||||
"cidrValid": "CIDR должен быть валидным"
|
||||
"cidrValid": "CIDR должен быть корректным"
|
||||
},
|
||||
"otl": "Одноразовая ссылка",
|
||||
"stringMalformed": "Строка имеет неверный формат",
|
||||
"body": "Тело должно быть объектом",
|
||||
"body": "Тело должно быть корректным объектом",
|
||||
"hook": "Хук",
|
||||
"enabled": "Включено",
|
||||
"mtu": "MTU",
|
||||
"port": "Порт",
|
||||
"persistentKeepalive": "Поддерживать соединение",
|
||||
"persistentKeepalive": "Постоянное поддержание соединения",
|
||||
"address": "IP‑адрес",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "Разрешённые IP",
|
||||
"allowedIps": "Разрешённые IP‑адреса",
|
||||
"file": "Файл"
|
||||
},
|
||||
"hooks": {
|
||||
@@ -236,5 +240,47 @@
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
},
|
||||
"copy": {
|
||||
"notSupported": "Копирование не поддерживается",
|
||||
"copied": "Скопировано!",
|
||||
"failed": "Ошибка копирования",
|
||||
"copy": "Копировать"
|
||||
},
|
||||
"awg": {
|
||||
"jCLabel": "Количество шумовых пакетов (Jc)",
|
||||
"jCDescription": "Число шумовых пакетов для отправки (1-128, рекомендуется: 4-12)",
|
||||
"jMinLabel": "Минимальный размер шумовых пакетов (Jmin)",
|
||||
"jMinDescription": "Минимальный размер шумовых пакетов (0-1279*, рекомендуется: 8, должен быть < Jmax)",
|
||||
"jMaxLabel": "Максимальный размер шумовых пакетов (Jmax)",
|
||||
"jMaxDescription": "Максимальный размер шумовых пакетов (1-1280*, рекомендуется: 80, должен быть > Jmin)",
|
||||
"s1Label": "Размер шумовых данных в init-пакете (S1)",
|
||||
"s1Description": "Размер шумовых данных в init-пакете (0-1132[1280* - 148 = 1132], рекомендуется: 15-150, S1+56 ≠ S2)",
|
||||
"s2Label": "Размер шумовых данных в ответном пакете (S2)",
|
||||
"s2Description": "Размер шумовых данных в ответном пакете (0-1188[1280* - 92 = 1188], рекомендуется: 15-150)",
|
||||
"s3Label": "Размер шумовых данных в cookie-reply пакете (S3)",
|
||||
"s3Description": "Размер шумовых данных в cookie-reply пакете",
|
||||
"s4Label": "Размер шумовых данных в транспортном пакете (S4)",
|
||||
"s4Description": "Размер шумовых данных в транспортном пакете",
|
||||
"i1Label": "Специальный шумовой пакет 1 (I1)",
|
||||
"i1Description": "Пакет имитации протокола в hex формате: <b 0x...>",
|
||||
"i2Label": "Специальный шумовой пакет 2 (I2)",
|
||||
"i2Description": "Пакет имитации протокола в hex формате: <b 0x...>",
|
||||
"i3Label": "Специальный шумовой пакет 3 (I3)",
|
||||
"i3Description": "Пакет имитации протокола в hex формате: <b 0x...>",
|
||||
"i4Label": "Специальный шумовой пакет 4 (I4)",
|
||||
"i4Description": "Пакет имитации протокола в hex формате: <b 0x...>",
|
||||
"i5Label": "Специальный шумовой пакет 5 (I5)",
|
||||
"i5Description": "Пакет имитации протокола в hex формате: <b 0x...>",
|
||||
"h1Label": "Init magic заголовок (H1)",
|
||||
"h1Description": "Значение заголовка init-пакета (5-2147483647, должно отличаться от H2-H4)",
|
||||
"h2Label": "Response magic заголовок (H2)",
|
||||
"h2Description": "Значение заголовка ответного пакета (5-2147483647, должно отличаться от H1, H3, H4)",
|
||||
"h3Label": "Cookie reply magic заголовок (H3)",
|
||||
"h3Description": "Значение заголовка cookie-reply пакета (5-2147483647, должно отличаться от H1, H2, H4)",
|
||||
"h4Label": "Transport magic заголовок (H4)",
|
||||
"h4Description": "Значение заголовка транспортного пакета (5-2147483647, должно отличаться от H1-H3)",
|
||||
"mtuNote": "Значения зависят от MTU",
|
||||
"obfuscationParameters": "Параметры обфускации AmneziaWG"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"name": "Ім'я",
|
||||
"expireDate": "Термін дії",
|
||||
"expireDateDesc": "Дата, коли клієнт буде відключений. Порожнє для постійного користування",
|
||||
"delete": "Видалити",
|
||||
"deleteClient": "Видалити клієнта",
|
||||
"deleteDialog1": "Ви впевнені, що бажаєте видалити",
|
||||
"deleteDialog2": "Цю дію неможливо скасувати.",
|
||||
@@ -116,7 +117,10 @@
|
||||
"dnsDesc": "DNS сервер, який використовуватимуть клієнти (перевизначає глобальну конфігурацію)",
|
||||
"notConnected": "Клієнт не підключений",
|
||||
"endpoint": "Кінцева точка",
|
||||
"endpointDesc": "IP-адреса клієнта, з якої встановлюється з’єднання WireGuard"
|
||||
"endpointDesc": "IP-адреса клієнта, з якої встановлюється з’єднання WireGuard",
|
||||
"search": "Пошук клієнтів...",
|
||||
"config": "Конфігурація",
|
||||
"viewConfig": "Переглянути конфігурацію"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Змінити",
|
||||
@@ -236,5 +240,47 @@
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
},
|
||||
"copy": {
|
||||
"notSupported": "Копіювання не підтримується",
|
||||
"copied": "Скопійовано!",
|
||||
"failed": "Не вдалося скопіювати",
|
||||
"copy": "Копіювати"
|
||||
},
|
||||
"awg": {
|
||||
"jCLabel": "Кількість сміттєвих пакетів (Jc)",
|
||||
"jCDescription": "Кількість сміттєвих пакетів для відправки (1–128, рекомендовано: 4–12)",
|
||||
"jMinLabel": "Мінімальний розмір сміттєвого пакета (Jmin)",
|
||||
"jMinDescription": "Мінімальний розмір сміттєвих пакетів (0–1279*, рекомендовано: 8, має бути < Jmax)",
|
||||
"jMaxLabel": "Максимальний розмір сміттєвого пакета (Jmax)",
|
||||
"jMaxDescription": "Максимальний розмір сміттєвих пакетів (1–1280*, рекомендовано: 80, має бути > Jmin)",
|
||||
"s1Label": "Розмір сміттєвих даних у початковому пакеті (S1)",
|
||||
"s1Description": "Розмір сміттєвих даних у початковому пакеті (0–1132 [1280* - 148 = 1132], рекомендовано: 15–150, S1+56 ≠ S2)",
|
||||
"s2Label": "Розмір сміттєвих даних у пакеті відповіді (S2)",
|
||||
"s2Description": "Розмір сміттєвих даних у пакеті відповіді (0–1188 [1280* - 92 = 1188], рекомендовано: 15–150)",
|
||||
"s3Label": "Розмір сміттєвих даних у пакеті «cookie reply» (S3)",
|
||||
"s3Description": "Розмір сміттєвих даних у пакеті «cookie reply»",
|
||||
"s4Label": "Розмір сміттєвих даних у транспортному пакеті (S4)",
|
||||
"s4Description": "Розмір сміттєвих даних у транспортному пакеті",
|
||||
"i1Label": "Спеціальний сміттєвий пакет 1 (I1)",
|
||||
"i1Description": "Пакет-імітація протоколу у hex-форматі: <b 0x...>",
|
||||
"i2Label": "Спеціальний сміттєвий пакет 2 (I2)",
|
||||
"i2Description": "Пакет-імітація протоколу у hex-форматі: <b 0x...>",
|
||||
"i3Label": "Спеціальний сміттєвий пакет 3 (I3)",
|
||||
"i3Description": "Пакет-імітація протоколу у hex-форматі: <b 0x...>",
|
||||
"i4Label": "Спеціальний сміттєвий пакет 4 (I4)",
|
||||
"i4Description": "Пакет-імітація протоколу у hex-форматі: <b 0x...>",
|
||||
"i5Label": "Спеціальний сміттєвий пакет 5 (I5)",
|
||||
"i5Description": "Пакет-імітація протоколу у hex-форматі: <b 0x...>",
|
||||
"h1Label": "Початковий магічний заголовок (H1)",
|
||||
"h1Description": "Значення заголовка початкового пакета (5–2147483647, має бути унікальним від H2–H4)",
|
||||
"h2Label": "Магічний заголовок відповіді (H2)",
|
||||
"h2Description": "Значення заголовка пакета відповіді (5–2147483647, має бути унікальним від H1, H3, H4)",
|
||||
"h3Label": "Магічний заголовок «cookie reply» (H3)",
|
||||
"h3Description": "Значення заголовка пакета «cookie reply» (5–2147483647, має бути унікальним від H1, H2, H4)",
|
||||
"h4Label": "Магічний заголовок транспортного пакета (H4)",
|
||||
"h4Description": "Значення заголовка транспортного пакета (5–2147483647, має бути унікальним від H1–H3)",
|
||||
"mtuNote": "Значення залежать від MTU",
|
||||
"obfuscationParameters": "Параметри обфускації AmneziaWG"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,9 @@
|
||||
"name": "客户端名称",
|
||||
"expireDate": "过期日期",
|
||||
"expireDateDesc": "客户端将被自动禁用的日期。留空表示永久有效",
|
||||
"delete": "删除客户端",
|
||||
"deleteClient": "删除客户端",
|
||||
"deleteDialog1": "您确定要删除此客户端吗?",
|
||||
"deleteDialog1": "您确定要删除客户端",
|
||||
"deleteDialog2": "此操作无法撤销。",
|
||||
"enabled": "已启用",
|
||||
"address": "IP地址",
|
||||
@@ -113,7 +114,13 @@
|
||||
"hooks": "钩子脚本",
|
||||
"hooksDescription": "钩子脚本仅在使用wg-quick时有效",
|
||||
"hooksLeaveEmpty": "如果不使用wg-quick,请留空此字段",
|
||||
"search": "搜索客户端..."
|
||||
"dnsDesc": "客户端将使用的 DNS 服务器(将覆盖全局配置)",
|
||||
"notConnected": "客户端未连接",
|
||||
"endpoint": "端点",
|
||||
"endpointDesc": "建立 WireGuard 连接时客户端的 IP 地址",
|
||||
"search": "搜索客户端...",
|
||||
"config": "配置",
|
||||
"viewConfig": "查看配置文本"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "确认修改",
|
||||
@@ -233,5 +240,47 @@
|
||||
"postUp": "启动后脚本",
|
||||
"preDown": "停止前脚本",
|
||||
"postDown": "停止后脚本"
|
||||
},
|
||||
"copy": {
|
||||
"notSupported": "不支持复制",
|
||||
"copied": "已复制!",
|
||||
"failed": "复制失败",
|
||||
"copy": "复制"
|
||||
},
|
||||
"awg": {
|
||||
"jCLabel": "垃圾数据包计数(Jc)",
|
||||
"jCDescription": "发送的垃圾数据包数量(范围:1-128,推荐值:4-12)",
|
||||
"jMinLabel": "垃圾数据包最小尺寸(Jmin)",
|
||||
"jMinDescription": "垃圾数据包的最小尺寸(范围:0-1279*,推荐值:8,必须小于 Jmax)",
|
||||
"jMaxLabel": "垃圾数据包最大尺寸(Jmax)",
|
||||
"jMaxDescription": "垃圾数据包的最大尺寸(范围:1-1280*,推荐值:80,必须大于 Jmin)",
|
||||
"s1Label": "初始数据包垃圾数据大小(S1)",
|
||||
"s1Description": "初始数据包中垃圾数据的大小(范围:0-1132[1280* - 148 = 1132],推荐值:15-150,S1+56 ≠ S2)",
|
||||
"s2Label": "响应数据包垃圾数据大小(S2)",
|
||||
"s2Description": "响应数据包中垃圾数据的大小(范围:0-1188[1280* - 92 = 1188],推荐值:15-150)",
|
||||
"s3Label": "Cookie 回复数据包垃圾数据大小(S3)",
|
||||
"s3Description": "Cookie 回复数据包中垃圾数据的大小",
|
||||
"s4Label": "传输数据包垃圾数据大小(S4)",
|
||||
"s4Description": "传输数据包中垃圾数据的大小",
|
||||
"i1Label": "特殊垃圾数据包 1(I1)",
|
||||
"i1Description": "协议模拟数据包(十六进制格式):<b 0x...>",
|
||||
"i2Label": "特殊垃圾数据包 2(I2)",
|
||||
"i2Description": "协议模拟数据包(十六进制格式):<b 0x...>",
|
||||
"i3Label": "特殊垃圾数据包 3(I3)",
|
||||
"i3Description": "协议模拟数据包(十六进制格式):<b 0x...>",
|
||||
"i4Label": "特殊垃圾数据包 4(I4)",
|
||||
"i4Description": "协议模拟数据包(十六进制格式):<b 0x...>",
|
||||
"i5Label": "特殊垃圾数据包 5(I5)",
|
||||
"i5Description": "协议模拟数据包(十六进制格式):<b 0x...>",
|
||||
"h1Label": "初始数据包魔术头部(H1)",
|
||||
"h1Description": "初始数据包头部值(范围:5-2147483647,必须与 H2-H4 不同)",
|
||||
"h2Label": "响应数据包魔术头部(H2)",
|
||||
"h2Description": "响应数据包头部值(范围:5-2147483647,必须与 H1、H3、H4 不同)",
|
||||
"h3Label": "Cookie 回复数据包魔术头部(H3)",
|
||||
"h3Description": "Cookie 回复数据包头部值(范围:5-2147483647,必须与 H1、H2、H4 不同)",
|
||||
"h4Label": "传输数据包魔术头部(H4)",
|
||||
"h4Description": "传输数据包头部值(范围:5-2147483647,必须与 H1-H3 不同)",
|
||||
"mtuNote": "具体数值取决于 MTU(最大传输单元)",
|
||||
"obfuscationParameters": "AmneziaWG 混淆参数"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "帳戶",
|
||||
"clients": "用戶端",
|
||||
"admin": {
|
||||
"panel": "管理面板",
|
||||
"general": "一般設定",
|
||||
"config": "組態設定",
|
||||
"interface": "介面設定",
|
||||
"hooks": "Hook 設定"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "電子郵件"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "目前密碼",
|
||||
"enable2fa": "啟用兩步驟驗證",
|
||||
"enable2faDesc": "請使用您的驗證碼應用程式掃描 QR Code,或手動輸入金鑰。",
|
||||
"2faKey": "TOTP 金鑰",
|
||||
"2faCodeDesc": "請輸入驗證碼應用程式提供的驗證碼。",
|
||||
"disable2fa": "停用兩步驟驗證",
|
||||
"disable2faDesc": "請輸入您的密碼以停用兩步驟驗證。"
|
||||
},
|
||||
"general": {
|
||||
"name": "名稱",
|
||||
"username": "使用者名稱",
|
||||
"password": "密碼",
|
||||
"newPassword": "新密碼",
|
||||
"updatePassword": "更新密碼",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "允許的 IP",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "保持連線",
|
||||
"logout": "登出",
|
||||
"continue": "繼續",
|
||||
"host": "主機",
|
||||
"port": "連接埠",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"confirmPassword": "確認密碼",
|
||||
"loading": "正在載入...",
|
||||
"2fa": "兩步驟驗證",
|
||||
"2faCode": "TOTP 驗證碼"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "歡迎首次設定您的 wg-easy",
|
||||
"welcomeDesc": "這是您在任何 Linux 主機上安裝與管理 WireGuard 最簡單的方式",
|
||||
"existingSetup": "您已有現存的設定了嗎?",
|
||||
"createAdminDesc": "請先輸入管理員使用者名稱與高強度密碼。此資訊將用於登入管理面板。",
|
||||
"setupConfigDesc": "請輸入主機與連接埠資訊。此資訊將用於設定用戶端的 WireGuard 連線。",
|
||||
"setupMigrationDesc": "若要從先前的 wg-easy 版本移轉資料,請提供備份檔案。",
|
||||
"upload": "上傳",
|
||||
"migration": "還原備份:",
|
||||
"createAccount": "建立帳戶",
|
||||
"successful": "設定成功",
|
||||
"hostDesc": "用戶端將連線的公開主機名稱",
|
||||
"portDesc": "用戶端將連線的公開 UDP 連接埠,且 WireGuard 會在此監聽"
|
||||
},
|
||||
"update": {
|
||||
"updateAvailable": "已有更新可供使用!",
|
||||
"update": "更新"
|
||||
},
|
||||
"theme": {
|
||||
"dark": "深色佈景主題",
|
||||
"light": "淺色佈景主題",
|
||||
"system": "系統佈景主題"
|
||||
},
|
||||
"layout": {
|
||||
"toggleCharts": "顯示/隱藏圖表",
|
||||
"donate": "贊助"
|
||||
},
|
||||
"login": {
|
||||
"signIn": "登入",
|
||||
"rememberMe": "記住我",
|
||||
"rememberMeDesc": "關閉瀏覽器後仍保持登入狀態",
|
||||
"insecure": "您無法在不安全的連線下登入。請使用 HTTPS。",
|
||||
"2faRequired": "需要兩步驟驗證",
|
||||
"2faWrong": "兩步驟驗證碼不正確"
|
||||
},
|
||||
"client": {
|
||||
"empty": "尚無用戶端。",
|
||||
"newShort": "新增",
|
||||
"sort": "排序",
|
||||
"create": "建立用戶端",
|
||||
"created": "已建立用戶端",
|
||||
"new": "新增用戶端",
|
||||
"name": "名稱",
|
||||
"expireDate": "到期日",
|
||||
"expireDateDesc": "用戶端將被停用的日期。留白表示永久有效",
|
||||
"delete": "刪除",
|
||||
"deleteClient": "刪除用戶端",
|
||||
"deleteDialog1": "您確定要刪除",
|
||||
"deleteDialog2": "此動作無法復原。",
|
||||
"enabled": "啟用",
|
||||
"address": "位址",
|
||||
"serverAllowedIps": "伺服器允許的 IP",
|
||||
"otlDesc": "產生暫時性單次連結",
|
||||
"permanent": "永久",
|
||||
"createdOn": "建立於 ",
|
||||
"lastSeen": "上次連線於 ",
|
||||
"totalDownload": "總下載量: ",
|
||||
"totalUpload": "總上傳量: ",
|
||||
"newClient": "新增用戶端",
|
||||
"disableClient": "停用用戶端",
|
||||
"enableClient": "啟用用戶端",
|
||||
"noPrivKey": "此用戶端沒有已知的私密金鑰,無法建立設定。",
|
||||
"showQR": "顯示 QR Code",
|
||||
"downloadConfig": "下載組態設定檔",
|
||||
"allowedIpsDesc": "將透過 VPN 路由的 IP (會覆寫全域設定)",
|
||||
"serverAllowedIpsDesc": "伺服器將路由至用戶端的 IP",
|
||||
"mtuDesc": "設定 VPN 通道的最大傳輸單位 (封包大小)",
|
||||
"persistentKeepaliveDesc": "Keep-alive 封包的間隔秒數。0 表示停用",
|
||||
"hooks": "Hook 設定",
|
||||
"hooksDescription": "Hook 設定僅適用於 wg-quick",
|
||||
"hooksLeaveEmpty": "僅適用於 wg-quick,否則請保持空白",
|
||||
"dnsDesc": "用戶端使用的 DNS 伺服器 (會覆寫全域設定)",
|
||||
"notConnected": "用戶端未連線",
|
||||
"endpoint": "端點",
|
||||
"endpointDesc": "用戶端建立 WireGuard 連線的來源 IP",
|
||||
"search": "搜尋用戶端...",
|
||||
"config": "組態設定",
|
||||
"viewConfig": "檢視組態設定"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "變更",
|
||||
"cancel": "取消",
|
||||
"create": "建立"
|
||||
},
|
||||
"toast": {
|
||||
"success": "成功",
|
||||
"saved": "已儲存",
|
||||
"error": "錯誤"
|
||||
},
|
||||
"form": {
|
||||
"actions": "操作",
|
||||
"save": "儲存",
|
||||
"revert": "還原",
|
||||
"sectionGeneral": "一般設定",
|
||||
"sectionAdvanced": "進階設定",
|
||||
"noItems": "沒有項目",
|
||||
"nullNoItems": "沒有項目。使用全域設定",
|
||||
"add": "新增"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "工作階段逾時",
|
||||
"sessionTimeoutDesc": "「記住我」的工作階段持續時間 (秒)",
|
||||
"metrics": "計量",
|
||||
"metricsPassword": "密碼",
|
||||
"metricsPasswordDesc": "計量端點的 Bearer 密碼 (密碼或 argon2 雜湊)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "提供 JSON 格式計量的路由",
|
||||
"prometheus": "Prometheus",
|
||||
"prometheusDesc": "提供 Prometheus 計量的路由"
|
||||
},
|
||||
"config": {
|
||||
"connection": "連線",
|
||||
"hostDesc": "用戶端將連線的公開主機名稱 (變更後會使目前組態設定檔失效)",
|
||||
"portDesc": "用戶端將連線的公開 UDP 連接埠 (變更後會使目前組態設定檔失效,您可能也需要變更介面連接埠)",
|
||||
"allowedIpsDesc": "用戶端將使用的允許 IP (全域設定)",
|
||||
"dnsDesc": "用戶端將使用的 DNS 伺服器 (全域設定)",
|
||||
"mtuDesc": "用戶端使用的 MTU (僅適用於新用戶端)",
|
||||
"persistentKeepaliveDesc": "傳送 keepalive 的間隔秒數。以 0 表示停用 (僅適用於新用戶端)",
|
||||
"suggest": "建議",
|
||||
"suggestDesc": "為主機欄位選擇 IP 位址或主機名稱"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "已變更 CIDR",
|
||||
"device": "裝置",
|
||||
"deviceDesc": "用於轉送 WireGuard 流量的乙太網路裝置",
|
||||
"mtuDesc": "WireGuard 將使用的 MTU",
|
||||
"portDesc": "WireGuard 監聽的 UDP 連接埠 (您可能也需要變更連接埠組態設定檔)",
|
||||
"changeCidr": "變更 CIDR",
|
||||
"restart": "重新啟動介面",
|
||||
"restartDesc": "重新啟動 WireGuard 介面",
|
||||
"restartWarn": "您確定要重新啟動介面嗎? 所有用戶端將被中斷連線。",
|
||||
"restartSuccess": "介面已重新啟動"
|
||||
},
|
||||
"introText": "歡迎使用管理面板。\n\n您可在此管理一般、組態、介面與 Hook 設定。\n\n請從側邊欄選擇任一項目開始。"
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
"required": "{0} 為必填項目",
|
||||
"validNumber": "{0} 必須為有效的數字",
|
||||
"validString": "{0} 必須為有效的字串",
|
||||
"validBoolean": "{0} 必須為有效的布林值",
|
||||
"validArray": "{0} 必須為有效的陣列",
|
||||
"stringMin": "{0} 至少需要 {1} 個字元",
|
||||
"numberMin": "{0} 不能小於 {1}"
|
||||
},
|
||||
"client": {
|
||||
"id": "用戶端 ID",
|
||||
"name": "名稱",
|
||||
"expiresAt": "到期時間",
|
||||
"address4": "IPv4 位址",
|
||||
"address6": "IPv6 位址",
|
||||
"serverAllowedIps": "伺服器允許的 IP"
|
||||
},
|
||||
"user": {
|
||||
"username": "使用者名稱",
|
||||
"password": "密碼",
|
||||
"remember": "記住我",
|
||||
"name": "名稱",
|
||||
"email": "電子郵件",
|
||||
"emailInvalid": "電子郵件格式無效",
|
||||
"passwordMatch": "密碼必須一致",
|
||||
"totpEnable": "啟用 TOTP",
|
||||
"totpEnableTrue": "必須啟用 TOTP",
|
||||
"totpCode": "TOTP 驗證碼"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "主機"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "工作階段逾時",
|
||||
"metricsEnabled": "計量",
|
||||
"metricsPassword": "計量密碼"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "裝置",
|
||||
"cidrValid": "CIDR 格式無效"
|
||||
},
|
||||
"otl": "單次連結",
|
||||
"stringMalformed": "字串格式錯誤",
|
||||
"body": "Body 必須為有效的物件",
|
||||
"hook": "Hook",
|
||||
"enabled": "啟用",
|
||||
"mtu": "MTU",
|
||||
"port": "連接埠",
|
||||
"persistentKeepalive": "保持連線",
|
||||
"address": "IP 位址",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "允許的 IP",
|
||||
"file": "檔案"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
},
|
||||
"copy": {
|
||||
"notSupported": "無法複製",
|
||||
"copied": "已複製!",
|
||||
"failed": "複製失敗",
|
||||
"copy": "複製"
|
||||
},
|
||||
"awg": {
|
||||
"jCLabel": "填充封包數量 (Jc)",
|
||||
"jCDescription": "要傳送的填充封包數量 (1-128,建議: 4-12)",
|
||||
"jMinLabel": "填充封包最小大小 (Jmin)",
|
||||
"jMinDescription": "填充封包的最小大小 (0-1279*,建議: 8,必須小於 Jmax)",
|
||||
"jMaxLabel": "填充封包最大大小 (Jmax)",
|
||||
"jMaxDescription": "填充封包的最大大小 (1-1280*,建議: 80,必須大於 Jmin)",
|
||||
"s1Label": "初始封包填充大小 (S1)",
|
||||
"s1Description": "初始封包填充大小 (0-1132 [1280* - 148 = 1132],建議: 15-150,S1+56 ≠ S2)",
|
||||
"s2Label": "回應封包填充大小 (S2)",
|
||||
"s2Description": "回應封包填充大小 (0-1188 [1280* - 92 = 1188],建議: 15-150)",
|
||||
"s3Label": "Cookie 回覆封包填充大小 (S3)",
|
||||
"s3Description": "Cookie 回覆封包填充大小",
|
||||
"s4Label": "傳輸封包填充大小 (S4)",
|
||||
"s4Description": "傳輸封包填充大小",
|
||||
"i1Label": "特殊填充封包 1 (I1)",
|
||||
"i1Description": "協定模仿封包 (16 進位格式): <b 0x...>",
|
||||
"i2Label": "特殊填充封包 2 (I2)",
|
||||
"i2Description": "協定模仿封包 (16 進位格式): <b 0x...>",
|
||||
"i3Label": "特殊填充封包 3 (I3)",
|
||||
"i3Description": "協定模仿封包 (16 進位格式): <b 0x...>",
|
||||
"i4Label": "特殊填充封包 4 (I4)",
|
||||
"i4Description": "協定模仿封包 (16 進位格式): <b 0x...>",
|
||||
"i5Label": "特殊填充封包 5 (I5)",
|
||||
"i5Description": "協定模仿封包 (16 進位格式): <b 0x...>",
|
||||
"h1Label": "初始特徵標頭 (H1)",
|
||||
"h1Description": "初始封包標頭值 (5-2147483647,必須與 H2-H4 不同)",
|
||||
"h2Label": "回應特徵標頭 (H2)",
|
||||
"h2Description": "回應封包標頭值 (5-2147483647,必須與 H1、H3、H4 不同)",
|
||||
"h3Label": "Cookie 回覆特徵標頭 (H3)",
|
||||
"h3Description": "Cookie 回覆封包標頭值 (5-2147483647,必須與 H1、H2、H4 不同)",
|
||||
"h4Label": "傳輸特徵標頭 (H4)",
|
||||
"h4Description": "傳輸封包標頭值 (5-2147483647,必須與 H1-H3 不同)",
|
||||
"mtuNote": "數值取決於 MTU",
|
||||
"obfuscationParameters": "AmneziaWG 混淆參數"
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,11 @@ export default defineNuxtConfig({
|
||||
language: 'zh-HK',
|
||||
name: '繁體中文(香港)',
|
||||
},
|
||||
{
|
||||
code: 'zh-TW',
|
||||
language: 'zh-TW',
|
||||
name: '正體中文 (台灣)',
|
||||
},
|
||||
{
|
||||
code: 'pl',
|
||||
language: 'pl-PL',
|
||||
|
||||
+20
-20
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wg-easy",
|
||||
"version": "15.2.0-beta.1",
|
||||
"version": "15.2.0",
|
||||
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -22,14 +22,14 @@
|
||||
"dependencies": {
|
||||
"@eschricht/nuxt-color-mode": "^1.2.0",
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@libsql/client": "^0.15.15",
|
||||
"@nuxtjs/i18n": "^10.2.0",
|
||||
"@libsql/client": "^0.17.0",
|
||||
"@nuxtjs/i18n": "^10.2.1",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
"@phc/format": "^1.0.0",
|
||||
"@pinia/nuxt": "^0.11.3",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"@vueuse/nuxt": "^14.0.0",
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
"@vueuse/nuxt": "^14.1.0",
|
||||
"apexcharts": "^5.3.6",
|
||||
"argon2": "^0.44.0",
|
||||
"cidr-tools": "^11.0.3",
|
||||
@@ -37,37 +37,37 @@
|
||||
"consola": "^3.4.2",
|
||||
"crc-32": "^1.2.2",
|
||||
"debug": "^4.4.3",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"ip-bigint": "^8.2.2",
|
||||
"is-cidr": "^6.0.1",
|
||||
"is-ip": "^5.0.1",
|
||||
"js-sha256": "^0.11.1",
|
||||
"nuxt": "^3.20.1",
|
||||
"nuxt": "^3.20.2",
|
||||
"otpauth": "^9.4.1",
|
||||
"pinia": "^3.0.4",
|
||||
"qr": "^0.5.2",
|
||||
"qr": "^0.5.4",
|
||||
"radix-vue": "^1.9.17",
|
||||
"semver": "^7.7.3",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"timeago.js": "^4.0.2",
|
||||
"vue": "latest",
|
||||
"vue3-apexcharts": "^1.10.0",
|
||||
"zod": "^4.1.12"
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint": "^1.10.0",
|
||||
"@nuxt/eslint": "^1.12.1",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/phc__format": "^1.0.1",
|
||||
"@types/semver": "^7.7.1",
|
||||
"drizzle-kit": "^0.31.6",
|
||||
"esbuild": "^0.27.0",
|
||||
"eslint": "^9.39.1",
|
||||
"drizzle-kit": "^0.31.8",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
||||
"tsx": "^4.20.6",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vue-tsc": "^3.1.3"
|
||||
"vue-tsc": "^3.2.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.21.0"
|
||||
"packageManager": "pnpm@10.28.0"
|
||||
}
|
||||
|
||||
Generated
+1834
-1745
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ export default definePermissionEventHandler(
|
||||
event,
|
||||
validateZod(InterfaceCidrUpdateSchema, event)
|
||||
);
|
||||
|
||||
await Database.interfaces.updateCidr(data);
|
||||
await WireGuard.saveConfig();
|
||||
return { success: true };
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
export default definePermissionEventHandler('admin', 'any', async () => {
|
||||
return {
|
||||
interface: {
|
||||
port: WG_OVERRIDE_ENV.PORT !== undefined,
|
||||
device: WG_OVERRIDE_ENV.DEVICE !== undefined,
|
||||
mtu: WG_OVERRIDE_ENV.MTU !== undefined,
|
||||
ipv4Cidr: WG_OVERRIDE_ENV.IPV4_CIDR !== undefined,
|
||||
ipv6Cidr: WG_OVERRIDE_ENV.IPV6_CIDR !== undefined,
|
||||
},
|
||||
userConfig: {
|
||||
host: WG_CLIENT_OVERRIDE_ENV.HOST !== undefined,
|
||||
port: WG_CLIENT_OVERRIDE_ENV.CLIENT_PORT !== undefined,
|
||||
defaultDns: WG_CLIENT_OVERRIDE_ENV.DEFAULT_DNS !== undefined,
|
||||
defaultAllowedIps:
|
||||
WG_CLIENT_OVERRIDE_ENV.DEFAULT_ALLOWED_IPS !== undefined,
|
||||
defaultMtu: WG_CLIENT_OVERRIDE_ENV.DEFAULT_MTU !== undefined,
|
||||
defaultPersistentKeepalive:
|
||||
WG_CLIENT_OVERRIDE_ENV.DEFAULT_PERSISTENT_KEEPALIVE !== undefined,
|
||||
},
|
||||
general: {
|
||||
sessionTimeout: WG_GENERAL_OVERRIDE_ENV.SESSION_TIMEOUT !== undefined,
|
||||
metricsPassword: WG_GENERAL_OVERRIDE_ENV.METRICS_PASSWORD !== undefined,
|
||||
metricsPrometheus:
|
||||
WG_GENERAL_OVERRIDE_ENV.METRICS_PROMETHEUS !== undefined,
|
||||
metricsJson: WG_GENERAL_OVERRIDE_ENV.METRICS_JSON !== undefined,
|
||||
},
|
||||
hooks: {
|
||||
preUp: WG_HOOKS_OVERRIDE_ENV.PRE_UP !== undefined,
|
||||
postUp: WG_HOOKS_OVERRIDE_ENV.POST_UP !== undefined,
|
||||
preDown: WG_HOOKS_OVERRIDE_ENV.PRE_DOWN !== undefined,
|
||||
postDown: WG_HOOKS_OVERRIDE_ENV.POST_DOWN !== undefined,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -8,19 +8,6 @@ export default defineSetupEventHandler(2, async ({ event }) => {
|
||||
|
||||
await Database.users.create(username, password);
|
||||
|
||||
// If host and port are already set by environment variables, skip step 4
|
||||
const host = WG_INITIAL_ENV.HOST ?? WG_CLIENT_OVERRIDE_ENV.HOST;
|
||||
const port = WG_INITIAL_ENV.PORT ?? WG_INTERFACE_OVERRIDE_ENV.PORT;
|
||||
|
||||
const setupDone = host && port;
|
||||
|
||||
if (setupDone) {
|
||||
// Skip to done
|
||||
await Database.general.setSetupStep(0);
|
||||
} else {
|
||||
// Proceed to step 3 (which leads to step 4)
|
||||
await Database.general.setSetupStep(3);
|
||||
}
|
||||
|
||||
return { success: true, setupDone: setupDone };
|
||||
await Database.general.setSetupStep(3);
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
@@ -175,30 +175,26 @@ export class ClientService {
|
||||
|
||||
return this.#db.transaction(async (tx) => {
|
||||
const clients = await tx.query.client.findMany().execute();
|
||||
const _clientInterface = await tx.query.wgInterface
|
||||
const clientInterface = await tx.query.wgInterface
|
||||
.findFirst({
|
||||
where: eq(wgInterface.name, 'wg0'),
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!_clientInterface) {
|
||||
if (!clientInterface) {
|
||||
throw new Error('WireGuard interface not found');
|
||||
}
|
||||
|
||||
const clientInterface = applyInterfaceOverrides(_clientInterface);
|
||||
|
||||
const _clientConfig = await tx.query.userConfig
|
||||
const clientConfig = await tx.query.userConfig
|
||||
.findFirst({
|
||||
where: eq(userConfig.id, clientInterface.name),
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!_clientConfig) {
|
||||
if (!clientConfig) {
|
||||
throw new Error('WireGuard interface configuration not found');
|
||||
}
|
||||
|
||||
const clientConfig = applyUserConfigOverrides(_clientConfig);
|
||||
|
||||
const ipv4Cidr = parseCidr(clientInterface.ipv4Cidr);
|
||||
const ipv4Address = nextIP(4, ipv4Cidr, clients);
|
||||
const ipv6Cidr = parseCidr(clientInterface.ipv6Cidr);
|
||||
@@ -245,18 +241,16 @@ export class ClientService {
|
||||
|
||||
update(id: ID, data: UpdateClientType) {
|
||||
return this.#db.transaction(async (tx) => {
|
||||
const _clientInterface = await tx.query.wgInterface
|
||||
const clientInterface = await tx.query.wgInterface
|
||||
.findFirst({
|
||||
where: eq(wgInterface.name, 'wg0'),
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!_clientInterface) {
|
||||
if (!clientInterface) {
|
||||
throw new Error('WireGuard interface not found');
|
||||
}
|
||||
|
||||
const clientInterface = applyInterfaceOverrides(_clientInterface);
|
||||
|
||||
if (!containsCidr(clientInterface.ipv4Cidr, data.ipv4Address)) {
|
||||
throw new Error('IPv4 address is not within the CIDR range');
|
||||
}
|
||||
@@ -278,8 +272,7 @@ export class ClientService {
|
||||
privateKey,
|
||||
publicKey,
|
||||
}: ClientCreateFromExistingType) {
|
||||
const _clientConfig = await Database.userConfigs.get();
|
||||
const clientConfig = applyUserConfigOverrides(_clientConfig);
|
||||
const clientConfig = await Database.userConfigs.get();
|
||||
|
||||
return this.#db
|
||||
.insert(client)
|
||||
|
||||
@@ -16,6 +16,12 @@ function createPreparedStatement(db: DBType) {
|
||||
oneTimeLink: sql.placeholder('oneTimeLink'),
|
||||
expiresAt: sql.placeholder('expiresAt'),
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: oneTimeLink.id,
|
||||
set: {
|
||||
expiresAt: sql.placeholder('expiresAt') as never as string,
|
||||
},
|
||||
})
|
||||
.prepare(),
|
||||
erase: db
|
||||
.update(oneTimeLink)
|
||||
|
||||
@@ -101,26 +101,22 @@ async function initialSetup(db: DBServiceType) {
|
||||
});
|
||||
}
|
||||
|
||||
if (WG_INITIAL_ENV.USERNAME && WG_INITIAL_ENV.PASSWORD) {
|
||||
if (
|
||||
WG_INITIAL_ENV.USERNAME &&
|
||||
WG_INITIAL_ENV.PASSWORD &&
|
||||
WG_INITIAL_ENV.HOST &&
|
||||
WG_INITIAL_ENV.PORT
|
||||
) {
|
||||
DB_DEBUG('Creating initial user...');
|
||||
await db.users.create(WG_INITIAL_ENV.USERNAME, WG_INITIAL_ENV.PASSWORD);
|
||||
|
||||
await db.general.setSetupStep(3);
|
||||
}
|
||||
|
||||
// Use INIT vars or fall back to override vars for HOST and PORT
|
||||
const host = WG_INITIAL_ENV.HOST ?? WG_CLIENT_OVERRIDE_ENV.HOST;
|
||||
const port = WG_INITIAL_ENV.PORT ?? WG_INTERFACE_OVERRIDE_ENV.PORT;
|
||||
|
||||
// HOST and PORT can come from either INIT vars or override vars
|
||||
if (host && port) {
|
||||
DB_DEBUG('Setting initial host and port...');
|
||||
await db.userConfigs.updateHostPort(host, port);
|
||||
await db.userConfigs.updateHostPort(
|
||||
WG_INITIAL_ENV.HOST,
|
||||
WG_INITIAL_ENV.PORT
|
||||
);
|
||||
|
||||
// Setup completion requires USERNAME and PASSWORD (no overrides for these)
|
||||
if (WG_INITIAL_ENV.USERNAME && WG_INITIAL_ENV.PASSWORD) {
|
||||
await db.general.setSetupStep(0);
|
||||
}
|
||||
await db.general.setSetupStep(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,17 +9,12 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
const { step, done } = await Database.general.getSetupStep();
|
||||
if (!done) {
|
||||
const parsedSetup = url.pathname.match(/\/setup\/(\d|migrate|success)/);
|
||||
const parsedSetup = url.pathname.match(/\/setup\/(\d)/);
|
||||
if (!parsedSetup) {
|
||||
return sendRedirect(event, `/setup/1`, 302);
|
||||
}
|
||||
const [_, currentSetup] = parsedSetup;
|
||||
|
||||
// Allow access to success page during setup
|
||||
if (currentSetup === 'success') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (step.toString() === currentSetup) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13,10 +13,7 @@ class WireGuard {
|
||||
* Save and sync config
|
||||
*/
|
||||
async saveConfig() {
|
||||
const wgInterface = applyInterfaceOverrides(
|
||||
await Database.interfaces.get()
|
||||
);
|
||||
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
await this.#saveWireguardConfig(wgInterface);
|
||||
await this.#syncWireguardConfig(wgInterface);
|
||||
}
|
||||
@@ -28,7 +25,7 @@ class WireGuard {
|
||||
*/
|
||||
async #saveWireguardConfig(wgInterface: InterfaceType) {
|
||||
const clients = await Database.clients.getAll();
|
||||
const hooks = applyHooksOverrides(await Database.hooks.get());
|
||||
const hooks = await Database.hooks.get();
|
||||
|
||||
const result = [];
|
||||
result.push(
|
||||
@@ -153,12 +150,8 @@ class WireGuard {
|
||||
}
|
||||
|
||||
async getClientConfiguration({ clientId }: { clientId: ID }) {
|
||||
const wgInterface = applyInterfaceOverrides(
|
||||
await Database.interfaces.get()
|
||||
);
|
||||
const userConfig = applyUserConfigOverrides(
|
||||
await Database.userConfigs.get()
|
||||
);
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
const userConfig = await Database.userConfigs.get();
|
||||
|
||||
const client = await Database.clients.get(clientId);
|
||||
|
||||
@@ -173,11 +166,24 @@ class WireGuard {
|
||||
|
||||
async getClientQRCodeSVG({ clientId }: { clientId: ID }) {
|
||||
const config = await this.getClientConfiguration({ clientId });
|
||||
return encodeQR(config, 'svg', {
|
||||
ecc: 'high',
|
||||
scale: 2,
|
||||
encoding: 'byte',
|
||||
});
|
||||
const ECMode = ['high', 'quartile', 'medium', 'low'] as const;
|
||||
for (const ecc of ECMode) {
|
||||
try {
|
||||
return encodeQR(config, 'svg', {
|
||||
ecc,
|
||||
scale: 2,
|
||||
encoding: 'byte',
|
||||
});
|
||||
} catch (err) {
|
||||
if (!(err instanceof Error && err.message === 'Capacity overflow')) {
|
||||
throw err;
|
||||
}
|
||||
// retry with lower ecc
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
'Failed to generate QR code: Capacity overflow at all ECC levels'
|
||||
);
|
||||
}
|
||||
|
||||
cleanClientFilename(name: string): string {
|
||||
@@ -224,33 +230,25 @@ class WireGuard {
|
||||
Database.interfaces.update(wgInterface);
|
||||
}
|
||||
|
||||
const wgInterfaceWithOverrides = applyInterfaceOverrides(wgInterface);
|
||||
|
||||
WG_DEBUG(
|
||||
`Starting Wireguard Interface ${wgInterfaceWithOverrides.name}...`
|
||||
);
|
||||
await this.#saveWireguardConfig(wgInterfaceWithOverrides);
|
||||
await wg.down(wgInterfaceWithOverrides.name).catch(() => {});
|
||||
await wg.up(wgInterfaceWithOverrides.name).catch((err) => {
|
||||
WG_DEBUG(`Starting Wireguard Interface ${wgInterface.name}...`);
|
||||
await this.#saveWireguardConfig(wgInterface);
|
||||
await wg.down(wgInterface.name).catch(() => {});
|
||||
await wg.up(wgInterface.name).catch((err) => {
|
||||
if (
|
||||
err &&
|
||||
err.message &&
|
||||
err.message.includes(
|
||||
`Cannot find device "${wgInterfaceWithOverrides.name}"`
|
||||
)
|
||||
err.message.includes(`Cannot find device "${wgInterface.name}"`)
|
||||
) {
|
||||
throw new Error(
|
||||
`WireGuard exited with the error: Cannot find device "${wgInterfaceWithOverrides.name}"\nThis usually means that your host's kernel does not support WireGuard!`,
|
||||
`WireGuard exited with the error: Cannot find device "${wgInterface.name}"\nThis usually means that your host's kernel does not support WireGuard!`,
|
||||
{ cause: err.message }
|
||||
);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
await this.#syncWireguardConfig(wgInterfaceWithOverrides);
|
||||
WG_DEBUG(
|
||||
`Wireguard Interface ${wgInterfaceWithOverrides.name} started successfully.`
|
||||
);
|
||||
await this.#syncWireguardConfig(wgInterface);
|
||||
WG_DEBUG(`Wireguard Interface ${wgInterface.name} started successfully.`);
|
||||
|
||||
WG_DEBUG('Starting Cron Job...');
|
||||
await this.startCronJob();
|
||||
@@ -269,16 +267,12 @@ class WireGuard {
|
||||
|
||||
// Shutdown wireguard
|
||||
async Shutdown() {
|
||||
const wgInterface = applyInterfaceOverrides(
|
||||
await Database.interfaces.get()
|
||||
);
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
await wg.down(wgInterface.name).catch(() => {});
|
||||
}
|
||||
|
||||
async Restart() {
|
||||
const wgInterface = applyInterfaceOverrides(
|
||||
await Database.interfaces.get()
|
||||
);
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
await wg.restart(wgInterface.name);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,78 +54,6 @@ export const WG_INITIAL_ENV = {
|
||||
: undefined,
|
||||
};
|
||||
|
||||
export const WG_INTERFACE_OVERRIDE_ENV = {
|
||||
/** Override the WireGuard interface port */
|
||||
PORT: process.env.WG_PORT
|
||||
? Number.parseInt(process.env.WG_PORT, 10)
|
||||
: undefined,
|
||||
/** Override the network device/interface */
|
||||
DEVICE: process.env.WG_DEVICE,
|
||||
/** Override the MTU setting */
|
||||
MTU: process.env.WG_MTU ? Number.parseInt(process.env.WG_MTU, 10) : undefined,
|
||||
/** Override the IPv4 CIDR */
|
||||
IPV4_CIDR: process.env.WG_IPV4_CIDR,
|
||||
/** Override the IPv6 CIDR */
|
||||
IPV6_CIDR: process.env.WG_IPV6_CIDR,
|
||||
};
|
||||
|
||||
export const WG_CLIENT_OVERRIDE_ENV = {
|
||||
/** Override the client connection host */
|
||||
HOST: process.env.WG_HOST,
|
||||
/** Override the client connection port (falls back to Interface Port) */
|
||||
CLIENT_PORT: process.env.WG_CLIENT_PORT
|
||||
? Number.parseInt(process.env.WG_CLIENT_PORT, 10)
|
||||
: WG_INTERFACE_OVERRIDE_ENV.PORT,
|
||||
/** Override default client DNS servers */
|
||||
DEFAULT_DNS: process.env.WG_DEFAULT_DNS?.split(',').map((x) => x.trim()),
|
||||
/** Override default client allowed IPs */
|
||||
DEFAULT_ALLOWED_IPS: process.env.WG_DEFAULT_ALLOWED_IPS?.split(',').map((x) =>
|
||||
x.trim()
|
||||
),
|
||||
/** Override default client MTU */
|
||||
DEFAULT_MTU: process.env.WG_DEFAULT_MTU
|
||||
? Number.parseInt(process.env.WG_DEFAULT_MTU, 10)
|
||||
: undefined,
|
||||
/** Override default client persistent keepalive */
|
||||
DEFAULT_PERSISTENT_KEEPALIVE: process.env.WG_DEFAULT_PERSISTENT_KEEPALIVE
|
||||
? Number.parseInt(process.env.WG_DEFAULT_PERSISTENT_KEEPALIVE, 10)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
export const WG_GENERAL_OVERRIDE_ENV = {
|
||||
/** Override session timeout */
|
||||
SESSION_TIMEOUT: process.env.WG_SESSION_TIMEOUT
|
||||
? Number.parseInt(process.env.WG_SESSION_TIMEOUT, 10)
|
||||
: undefined,
|
||||
/** Override metrics password */
|
||||
METRICS_PASSWORD: process.env.WG_METRICS_PASSWORD,
|
||||
/** Override metrics Prometheus enabled status */
|
||||
METRICS_PROMETHEUS:
|
||||
process.env.WG_METRICS_PROMETHEUS === 'true'
|
||||
? true
|
||||
: process.env.WG_METRICS_PROMETHEUS === 'false'
|
||||
? false
|
||||
: undefined,
|
||||
/** Override metrics JSON enabled status */
|
||||
METRICS_JSON:
|
||||
process.env.WG_METRICS_JSON === 'true'
|
||||
? true
|
||||
: process.env.WG_METRICS_JSON === 'false'
|
||||
? false
|
||||
: undefined,
|
||||
};
|
||||
|
||||
export const WG_HOOKS_OVERRIDE_ENV = {
|
||||
/** Override PreUp hook */
|
||||
PRE_UP: process.env.WG_PRE_UP,
|
||||
/** Override PostUp hook */
|
||||
POST_UP: process.env.WG_POST_UP,
|
||||
/** Override PreDown hook */
|
||||
PRE_DOWN: process.env.WG_PRE_DOWN,
|
||||
/** Override PostDown hook */
|
||||
POST_DOWN: process.env.WG_POST_DOWN,
|
||||
};
|
||||
|
||||
function assertEnv<T extends string>(env: T) {
|
||||
const val = process.env[env];
|
||||
|
||||
@@ -135,105 +63,3 @@ function assertEnv<T extends string>(env: T) {
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply environment variable overrides to an interface object
|
||||
*/
|
||||
export function applyInterfaceOverrides<
|
||||
T extends {
|
||||
port: number;
|
||||
device: string;
|
||||
mtu: number;
|
||||
ipv4Cidr: string;
|
||||
ipv6Cidr: string;
|
||||
},
|
||||
>(wgInterface: T): T {
|
||||
return {
|
||||
...wgInterface,
|
||||
port: WG_INTERFACE_OVERRIDE_ENV.PORT ?? wgInterface.port,
|
||||
device: WG_INTERFACE_OVERRIDE_ENV.DEVICE ?? wgInterface.device,
|
||||
mtu: WG_INTERFACE_OVERRIDE_ENV.MTU ?? wgInterface.mtu,
|
||||
ipv4Cidr: WG_INTERFACE_OVERRIDE_ENV.IPV4_CIDR ?? wgInterface.ipv4Cidr,
|
||||
ipv6Cidr: WG_INTERFACE_OVERRIDE_ENV.IPV6_CIDR ?? wgInterface.ipv6Cidr,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply environment variable overrides to a user config object
|
||||
*/
|
||||
export function applyUserConfigOverrides<
|
||||
T extends {
|
||||
host: string;
|
||||
port: number;
|
||||
defaultDns: string[];
|
||||
defaultAllowedIps: string[];
|
||||
defaultMtu: number;
|
||||
defaultPersistentKeepalive: number;
|
||||
},
|
||||
>(userConfig: T): T {
|
||||
return {
|
||||
...userConfig,
|
||||
host: WG_CLIENT_OVERRIDE_ENV.HOST ?? userConfig.host,
|
||||
port: WG_CLIENT_OVERRIDE_ENV.CLIENT_PORT ?? userConfig.port,
|
||||
defaultDns: WG_CLIENT_OVERRIDE_ENV.DEFAULT_DNS ?? userConfig.defaultDns,
|
||||
defaultAllowedIps:
|
||||
WG_CLIENT_OVERRIDE_ENV.DEFAULT_ALLOWED_IPS ??
|
||||
userConfig.defaultAllowedIps,
|
||||
defaultMtu: WG_CLIENT_OVERRIDE_ENV.DEFAULT_MTU ?? userConfig.defaultMtu,
|
||||
defaultPersistentKeepalive:
|
||||
WG_CLIENT_OVERRIDE_ENV.DEFAULT_PERSISTENT_KEEPALIVE ??
|
||||
userConfig.defaultPersistentKeepalive,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply environment variable overrides to a general config object
|
||||
*/
|
||||
export function applySessionOverrides<
|
||||
T extends {
|
||||
sessionTimeout: number;
|
||||
},
|
||||
>(generalConfig: T): T {
|
||||
return {
|
||||
...generalConfig,
|
||||
sessionTimeout:
|
||||
WG_GENERAL_OVERRIDE_ENV.SESSION_TIMEOUT ?? generalConfig.sessionTimeout,
|
||||
};
|
||||
}
|
||||
|
||||
export function applyMetricsOverrides<
|
||||
T extends {
|
||||
password: string | null;
|
||||
prometheus: boolean;
|
||||
json: boolean;
|
||||
},
|
||||
>(metricsConfig: T): T {
|
||||
return {
|
||||
...metricsConfig,
|
||||
password:
|
||||
WG_GENERAL_OVERRIDE_ENV.METRICS_PASSWORD ?? metricsConfig.password,
|
||||
prometheus:
|
||||
WG_GENERAL_OVERRIDE_ENV.METRICS_PROMETHEUS ?? metricsConfig.prometheus,
|
||||
json: WG_GENERAL_OVERRIDE_ENV.METRICS_JSON ?? metricsConfig.json,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply environment variable overrides to a hooks object
|
||||
*/
|
||||
export function applyHooksOverrides<
|
||||
T extends {
|
||||
preUp: string;
|
||||
postUp: string;
|
||||
preDown: string;
|
||||
postDown: string;
|
||||
},
|
||||
>(hooks: T): T {
|
||||
return {
|
||||
...hooks,
|
||||
preUp: WG_HOOKS_OVERRIDE_ENV.PRE_UP ?? hooks.preUp,
|
||||
postUp: WG_HOOKS_OVERRIDE_ENV.POST_UP ?? hooks.postUp,
|
||||
preDown: WG_HOOKS_OVERRIDE_ENV.PRE_DOWN ?? hooks.preDown,
|
||||
postDown: WG_HOOKS_OVERRIDE_ENV.POST_DOWN ?? hooks.postDown,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -138,9 +138,7 @@ export const defineMetricsHandler = <
|
||||
handler: MetricsHandler<TReq, TRes>
|
||||
) => {
|
||||
return defineEventHandler(async (event) => {
|
||||
const metricsConfig = applyMetricsOverrides(
|
||||
await Database.general.getMetricsConfig()
|
||||
);
|
||||
const metricsConfig = await Database.general.getMetricsConfig();
|
||||
|
||||
if (metricsConfig.password) {
|
||||
const auth = getHeader(event, 'Authorization');
|
||||
|
||||
@@ -8,10 +8,7 @@ export type WGSession = Partial<{
|
||||
const name = 'wg-easy';
|
||||
|
||||
export async function useWGSession(event: H3Event, rememberMe = false) {
|
||||
const sessionConfig = applySessionOverrides(
|
||||
await Database.general.getSessionConfig()
|
||||
);
|
||||
|
||||
const sessionConfig = await Database.general.getSessionConfig();
|
||||
return useSession<WGSession>(event, {
|
||||
password: sessionConfig.sessionPassword,
|
||||
name,
|
||||
@@ -25,10 +22,7 @@ export async function useWGSession(event: H3Event, rememberMe = false) {
|
||||
}
|
||||
|
||||
export async function getWGSession(event: H3Event) {
|
||||
const sessionConfig = applySessionOverrides(
|
||||
await Database.general.getSessionConfig()
|
||||
);
|
||||
|
||||
const sessionConfig = await Database.general.getSessionConfig();
|
||||
return getSession<WGSession>(event, {
|
||||
password: sessionConfig.sessionPassword,
|
||||
name,
|
||||
|
||||
Reference in New Issue
Block a user