Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da61cb680f | |||
| 8b5e6c4c7a | |||
| e192855280 | |||
| 3a52e1321b | |||
| b29703af86 | |||
| 516a3fa72c | |||
| e5cdb2b57a | |||
| 7fbc1cef68 | |||
| 432e7a8197 | |||
| d12045e10c | |||
| 015f3c5ba2 | |||
| e42f7313ab | |||
| 993c130f65 | |||
| fbf24410db | |||
| c1d5822f41 | |||
| 268916782d | |||
| 11ab71b5d2 | |||
| 67185192fd | |||
| 5fd3ee9843 | |||
| e444936c04 | |||
| 5d6c35b183 | |||
| edc3c5af57 | |||
| 26708305d6 | |||
| 6a282e6ab9 | |||
| a8ba7f7247 | |||
| 502fe718d5 | |||
| 5c7aac9fd2 | |||
| 2f96d9934b | |||
| daff15463d | |||
| 5f68d261c0 | |||
| 013ea6dba9 | |||
| ab9d75757f | |||
| 9be20109af | |||
| 9430b76258 | |||
| 99f1a004d5 | |||
| 2b42b639ea | |||
| 76d5944726 | |||
| 81bd19cfb6 | |||
| 0365ca7fb6 | |||
| 529d65b3fb | |||
| cbbf5d3d25 | |||
| 7b2d234ea5 | |||
| a282ca35f1 | |||
| 0792862c0d | |||
| 6c0d8e91fa | |||
| 8892c43a7d | |||
| 7cfe04286a | |||
| 000513f212 | |||
| 6ca3da1b80 | |||
| fe394ecbe4 | |||
| ec6f0423ca | |||
| e12208af75 | |||
| 2d9c75fd81 | |||
| 0c54b1c3da | |||
| be7943dc9b | |||
| 303c2f1e39 | |||
| 0b32ab899c | |||
| ef463d3d85 | |||
| c10daa2fd4 | |||
| cb8aa45cde | |||
| 54e0a1e886 | |||
| 71a452080e | |||
| 5be7fb3038 | |||
| 59f0c8b0d2 | |||
| e1ed93674d | |||
| 6b65a8099b | |||
| c1dd494d0f | |||
| bf9e8a6e21 | |||
| 371d7617ff | |||
| 0b435d9ed8 | |||
| 07f89d15a9 | |||
| b5318086d2 | |||
| b7f9b7c830 | |||
| 2e4f386f49 | |||
| 9ead985798 | |||
| 6326ee31c4 | |||
| 984dc95550 | |||
| cd0a9b8e33 | |||
| 90b9ba15ec | |||
| 0abc419db7 | |||
| b185d7a63d | |||
| 4bb880c4b7 | |||
| b0ba9e43f9 | |||
| ddb01fb968 | |||
| 22812e0632 | |||
| 4d84e1d9d3 |
@@ -27,17 +27,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
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@v4
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
@@ -98,6 +98,13 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Codeberg
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: codeberg.org
|
||||
username: ${{ secrets.CODEBERG_USER }}
|
||||
password: ${{ secrets.CODEBERG_PASS }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -107,6 +114,7 @@ jobs:
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
codeberg.org/wg-easy/wg-easy
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
@@ -130,10 +138,10 @@ jobs:
|
||||
contents: write
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11.9
|
||||
cache: "pip"
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: master
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
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@v4
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
@@ -107,6 +107,13 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Codeberg
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: codeberg.org
|
||||
username: ${{ secrets.CODEBERG_USER }}
|
||||
password: ${{ secrets.CODEBERG_PASS }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -116,6 +123,7 @@ jobs:
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
codeberg.org/wg-easy/wg-easy
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
@@ -139,12 +147,12 @@ jobs:
|
||||
contents: write
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11.9
|
||||
cache: "pip"
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
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@v4
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
@@ -108,6 +108,13 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Codeberg
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: codeberg.org
|
||||
username: ${{ secrets.CODEBERG_USER }}
|
||||
password: ${{ secrets.CODEBERG_PASS }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -117,6 +124,7 @@ jobs:
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
codeberg.org/wg-easy/wg-easy
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
@@ -144,10 +152,10 @@ jobs:
|
||||
contents: write
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11.9
|
||||
cache: "pip"
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -22,9 +22,9 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
node-version: "lts/jod"
|
||||
check-latest: true
|
||||
cache: "pnpm"
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -55,9 +55,9 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
node-version: "lts/jod"
|
||||
check-latest: true
|
||||
cache: "pnpm"
|
||||
|
||||
|
||||
@@ -15,13 +15,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'wg-easy'
|
||||
permissions:
|
||||
actions: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
# Stale after 30 days of inactivity
|
||||
days-before-issue-stale: 30
|
||||
# Close after 14 days of being stale
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||
@@ -32,3 +35,9 @@ jobs:
|
||||
close-pr-message: "This PR was closed because it has been inactive for 14 days since being marked as stale."
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
operations-per-run: 100
|
||||
# Ignore Feature requests (https://github.com/actions/stale/issues/1293)
|
||||
only-issue-types: "Bug"
|
||||
# Ignore confirmed bugs
|
||||
exempt-issue-labels: "status: confirmed"
|
||||
# Ignore PRs with milestones
|
||||
exempt-all-pr-milestones: true
|
||||
|
||||
Vendored
-1
@@ -4,7 +4,6 @@
|
||||
"dbaeumer.vscode-eslint",
|
||||
"antfu.goto-alias",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"Nuxtr.nuxtr-vscode",
|
||||
"esbenp.prettier-vscode",
|
||||
"yoavbls.pretty-ts-errors",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
|
||||
Vendored
-3
@@ -3,9 +3,6 @@
|
||||
"editor.useTabStops": false,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"nuxtr.vueFiles.style.addStyleTag": false,
|
||||
"nuxtr.piniaFiles.defaultTemplate": "setup",
|
||||
"nuxtr.monorepoMode.DirectoryName": "src",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "always"
|
||||
},
|
||||
|
||||
+33
-5
@@ -5,10 +5,38 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [15.1.0] - 2025-07-01
|
||||
## [Unreleased]
|
||||
|
||||
## 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)
|
||||
|
||||
## 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)
|
||||
|
||||
## 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)
|
||||
- Return client id on create (https://github.com/wg-easy/wg-easy/pull/2190)
|
||||
- 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)
|
||||
|
||||
## 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)
|
||||
|
||||
## [15.1.0] - 2025-07-01
|
||||
|
||||
### Added
|
||||
|
||||
- Added Ukrainian language (#1906)
|
||||
- Add French language (#1924)
|
||||
- docs for caddy example (#1939)
|
||||
@@ -18,12 +46,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add Chinese Simplified (#1990)
|
||||
- Add option to disable ipv6 (#1951)
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- Updated container launch commands (#1989)
|
||||
- update screenshot (962bfa2)
|
||||
|
||||
## Changed
|
||||
### Changed
|
||||
|
||||
- Updated dependencies
|
||||
|
||||
@@ -32,11 +60,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
We're super excited to announce v15!
|
||||
This update is an entire rewrite to make it even easier to set up your own VPN.
|
||||
|
||||
## Breaking Changes
|
||||
### Breaking Changes
|
||||
|
||||
As the whole setup has changed, we recommend to start from scratch. And import your existing configs.
|
||||
|
||||
## Major Changes
|
||||
### Major Changes
|
||||
|
||||
- Almost all Environment variables removed
|
||||
- New and Improved UI
|
||||
|
||||
+15
-2
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/library/node:lts-alpine AS build
|
||||
FROM docker.io/library/node:jod-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
# update corepack
|
||||
@@ -14,9 +14,15 @@ RUN pnpm install
|
||||
COPY src ./
|
||||
RUN pnpm build
|
||||
|
||||
# Build amneziawg-tools
|
||||
RUN apk add linux-headers build-base git && \
|
||||
git clone https://github.com/amnezia-vpn/amneziawg-tools.git && \
|
||||
cd amneziawg-tools/src && \
|
||||
make
|
||||
|
||||
# Copy build result to a new image.
|
||||
# This saves a lot of disk space.
|
||||
FROM docker.io/library/node:lts-alpine
|
||||
FROM docker.io/library/node:jod-alpine
|
||||
WORKDIR /app
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=5s --retries=3 CMD /usr/bin/timeout 5s /bin/sh -c "/usr/bin/wg show | /bin/grep -q interface || exit 1"
|
||||
@@ -32,6 +38,10 @@ RUN cd /app/server && \
|
||||
# cli
|
||||
COPY --from=build /app/cli/cli.sh /usr/local/bin/cli
|
||||
RUN chmod +x /usr/local/bin/cli
|
||||
# Copy amneziawg-tools
|
||||
COPY --from=build /app/amneziawg-tools/src/wg /usr/bin/awg
|
||||
COPY --from=build /app/amneziawg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
|
||||
RUN chmod +x /usr/bin/awg /usr/bin/awg-quick
|
||||
|
||||
# Install Linux packages
|
||||
RUN apk add --no-cache \
|
||||
@@ -44,6 +54,9 @@ RUN apk add --no-cache \
|
||||
iptables-legacy \
|
||||
wireguard-tools
|
||||
|
||||
RUN mkdir -p /etc/amnezia
|
||||
RUN ln -s /etc/wireguard /etc/amnezia/amneziawg
|
||||
|
||||
# Use iptables-legacy
|
||||
RUN update-alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables-legacy 10 --slave /usr/sbin/iptables-restore iptables-restore /usr/sbin/iptables-legacy-restore --slave /usr/sbin/iptables-save iptables-save /usr/sbin/iptables-legacy-save
|
||||
RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/library/node:lts-alpine
|
||||
FROM docker.io/library/node:jod-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# update corepack
|
||||
|
||||
@@ -68,11 +68,11 @@ And log in again.
|
||||
|
||||
The easiest way to run WireGuard Easy is with Docker Compose.
|
||||
|
||||
Just download [`docker-compose.yml`](docker-compose.yml) and execute `sudo docker compose up -d`.
|
||||
Just follow [these steps](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/) in the detailed documentation.
|
||||
|
||||
Now setup a reverse proxy to be able to access the Web UI securely from the internet.
|
||||
You can also install WireGuard Easy with the [docker run command](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/docker-run/) or via [podman](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/podman-nft/).
|
||||
|
||||
If you want to access the Web UI over HTTP, change the env var `INSECURE` to `true`. This is not recommended. Only use this for testing
|
||||
Now [setup a reverse proxy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/#setup-reverse-proxy) to be able to access the Web UI securely from the internet. This step is optional, just make sure to follow the guide [here](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/reverse-proxyless/) if you decide not to do it.
|
||||
|
||||
## Donate
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
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.
|
||||
|
||||
AmneziaWG adds multi-level transport-layer obfuscation by:
|
||||
|
||||
- Modifying packet headers
|
||||
- Randomizing handshake message sizes
|
||||
- Disguising traffic to resemble popular UDP protocols
|
||||
|
||||
These measures make it harder for third parties to analyze or identify your traffic, enhancing both privacy and security.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Android:
|
||||
|
||||
- [AmneziaWG](https://play.google.com/store/apps/details?id=org.amnezia.awg) - 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
|
||||
|
||||
Windows:
|
||||
|
||||
- [AmneziaWG](https://github.com/amnezia-vpn/amneziawg-windows-client/releases) - Official Client
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Experimental Configuration
|
||||
---
|
||||
|
||||
There are several experimental features that can be enabled by setting the appropriate environment variables. These features are not guaranteed to be stable and may change in future releases.
|
||||
|
||||
| Env | Default | Example | Description | Notes | More Info |
|
||||
| ---------------- | ------- | ------- | -------------------------------------- | --------------------------------------- | ------------------------ |
|
||||
| EXPERIMENTAL_AWG | false | true | Enables experimental AmneziaWG support | Planned to be enabled by default in v16 | [See here](./amnezia.md) |
|
||||
@@ -20,3 +20,74 @@ 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.
|
||||
|
||||
///
|
||||
|
||||
@@ -7,21 +7,24 @@ If you want to run the setup without any user interaction, e.g. with a tool like
|
||||
These will only be used during the first start of the container. After that, the setup will be disabled.
|
||||
|
||||
| Env | Example | Description | Group |
|
||||
| ---------------- | ----------------- | --------------------------------------------------------- | ----- |
|
||||
| ------------------ | ---------------------------- | --------------------------------------------------------- | ----- |
|
||||
| `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 | 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_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 |
|
||||
|
||||
/// warning | Variables have to be used together
|
||||
/// 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`.
|
||||
|
||||
If you want to skip the setup process, you have to configure group `1`
|
||||
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.
|
||||
///
|
||||
|
||||
/// note | Security
|
||||
|
||||
@@ -51,7 +51,9 @@ In the setup wizard, select that you already have a configuration file and uploa
|
||||
|
||||
### Environment Variables
|
||||
|
||||
v15 does not use the same environment variables as v14, most of them have been moved to the Admin Panel in the Web UI.
|
||||
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.
|
||||
|
||||
### Done
|
||||
|
||||
|
||||
@@ -2,8 +2,176 @@
|
||||
title: AdGuard Home
|
||||
---
|
||||
|
||||
It seems like the Docs on how to setup AdGuard Home are not available yet.
|
||||
This tutorial is a follow-up to the official [Traefik tutorial](./traefik.md). It will guide you through integrating AdGuard Home with your existing `wg-easy` and Traefik setup to provide network-wide DNS ad-blocking.
|
||||
|
||||
Feel free to create a PR and add them here.
|
||||
## Prerequisites
|
||||
|
||||
<!-- TODO -->
|
||||
- A working [wg-easy](./basic-installation.md) and [Traefik](./traefik.md) setup from the previous guides.
|
||||
|
||||
/// warning | Important: Following this guide will reset your WireGuard configuration.
|
||||
The process involves re-creating the `wg-easy` container and its data, which means **all existing WireGuard clients and settings will be deleted.**
|
||||
|
||||
You will need to create your clients again after completing this guide.
|
||||
///
|
||||
|
||||
## Add `adguard` configuration
|
||||
|
||||
1. Create a directory for the configuration files:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /etc/docker/containers/adguard
|
||||
```
|
||||
|
||||
2. Create volumes for persistent data:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /etc/docker/volumes/adguard/adguard_work
|
||||
sudo mkdir -p /etc/docker/volumes/adguard/adguard_conf
|
||||
sudo chmod -R 700 /etc/docker/volumes/adguard
|
||||
```
|
||||
|
||||
3. Create the `docker-compose.yml` file.
|
||||
|
||||
File: `/etc/docker/containers/adguard/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
adguard:
|
||||
image: adguard/adguardhome:v0.107.64
|
||||
container_name: adguard
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /etc/docker/volumes/adguard/adguard_work:/opt/adguardhome/work
|
||||
- /etc/docker/volumes/adguard/adguard_conf:/opt/adguardhome/conf
|
||||
networks:
|
||||
wg:
|
||||
interface_name: eth0
|
||||
ipv4_address: 10.42.42.43
|
||||
ipv6_address: fdcc:ad94:bacf:61a3::2b
|
||||
traefik:
|
||||
interface_name: eth1
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.adguard.rule=Host(`adguard.$example.com$`)'
|
||||
- 'traefik.http.routers.adguard.entrypoints=websecure'
|
||||
- 'traefik.http.routers.adguard.service=adguard'
|
||||
- 'traefik.http.services.adguard.loadbalancer.server.port=3000'
|
||||
- 'traefik.docker.network=traefik'
|
||||
|
||||
networks:
|
||||
wg:
|
||||
external: true
|
||||
traefik:
|
||||
external: true
|
||||
```
|
||||
|
||||
## Update `wg-easy` configuration
|
||||
|
||||
Modify the corresponding sections of your existing `wg-easy` compose file to match the updated version below.
|
||||
|
||||
File: `/etc/docker/containers/wg-easy/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wg-easy:
|
||||
ports:
|
||||
- "51820:51820/udp"
|
||||
...
|
||||
networks:
|
||||
wg:
|
||||
interface_name: eth0
|
||||
...
|
||||
traefik:
|
||||
interface_name: eth1
|
||||
...
|
||||
...
|
||||
environment:
|
||||
# Unattended Setup
|
||||
- INIT_ENABLED=true
|
||||
# Replace $username$ with your username
|
||||
- INIT_USERNAME=$username$
|
||||
# Replace $password$ with your unhashed password
|
||||
- INIT_PASSWORD=$password$
|
||||
# Replace $example.com$ with your domain
|
||||
- INIT_HOST=wg-easy.$example.com$
|
||||
- INIT_PORT=51820
|
||||
- INIT_DNS=10.42.42.43,fdcc:ad94:bacf:61a3::2b
|
||||
- INIT_IPV4_CIDR=10.8.0.0/24
|
||||
- INIT_IPV6_CIDR=fd42:42:42::/64
|
||||
...
|
||||
|
||||
networks:
|
||||
wg:
|
||||
# Prevents Docker Compose from prefixing the network name.
|
||||
name: wg
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
## Setup Wireguard
|
||||
|
||||
1. Restart `wg-easy`:
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker compose down -v
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
2. Edit Wireguard's Hooks.
|
||||
|
||||
In the Admin Panel of your WireGuard server, go to the Hooks tab and replace it with:
|
||||
|
||||
**_PostUp_**
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -t nat -A PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination 10.42.42.43; iptables -t nat -A PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination 10.42.42.43; ip6tables -t nat -A PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b; ip6tables -t nat -A PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; ip6tables -t nat -A POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE;
|
||||
```
|
||||
|
||||
**_PostDown_**
|
||||
|
||||
```shell
|
||||
iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT || true; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT || true; iptables -t nat -D PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination 10.42.42.43 || true; iptables -t nat -D PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination 10.42.42.43 || true; ip6tables -t nat -D PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b || true; ip6tables -t nat -D PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b || true; iptables -D FORWARD -i wg0 -j ACCEPT || true; iptables -D FORWARD -o wg0 -j ACCEPT || true; ip6tables -D FORWARD -i wg0 -j ACCEPT || true; ip6tables -D FORWARD -o wg0 -j ACCEPT || true; iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE || true; ip6tables -t nat -D POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE || true;
|
||||
```
|
||||
|
||||
3. Restart `wg-easy` to apply changes:
|
||||
|
||||
```shell
|
||||
sudo docker restart wg-easy
|
||||
```
|
||||
|
||||
## Setup Adguard Home
|
||||
|
||||
1. Start `adguard` service:
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/adguard
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
2. Navigate to `https://adguard.$example.com$` to begin the AdGuard Home setup.
|
||||
|
||||
/// warning | Important: Configure AdGuard Home Admin Web Interface Port
|
||||
During the initial AdGuard Home setup on the `Step 2/5` page, you **must** set the **Admin Web Interface Port** to **3000**. Do not use the default port 80, as it will not work with the Traefik configuration.
|
||||
|
||||
After completing the setup, the AdGuard UI might appear unresponsive. This is expected. **Simply reload the page**, and the panel will display correctly.
|
||||
///
|
||||
|
||||
> If you accidentally left it default (80), you will need to manually edit the `docker-compose.yml` file for AdGuard Home (`/etc/docker/containers/adguard/docker-compose.yml`) and change the line `traefik.http.services.adguard.loadbalancer.server.port=3000` to `traefik.http.services.adguard.loadbalancer.server.port=80`. After making this change, restart AdGuard Home by navigating to `/etc/docker/containers/adguard` and running `sudo docker compose up -d`.
|
||||
|
||||
## Final System Checks
|
||||
|
||||
### Firewall
|
||||
|
||||
Ensure the ports `80/tcp`, `443/tcp`, `443/udp`, and `51820/udp` are open.
|
||||
|
||||
### Optional: Optimizing UDP Buffer Sizes
|
||||
|
||||
AdGuard Home, as a DNS server, handles a large volume of UDP packets. To ensure optimal performance, it is recommended to increase the system's UDP buffer sizes. You can apply these settings using your system's `sysctl` configuration (e.g., by creating a file in `/etc/sysctl.d/`).
|
||||
|
||||
```shell
|
||||
net.core.rmem_max = 7500000
|
||||
net.core.wmem_max = 7500000
|
||||
```
|
||||
|
||||
After adding these settings, remember to apply them (e.g., by running `sudo sysctl --system` or rebooting)
|
||||
|
||||
@@ -7,7 +7,7 @@ title: Caddy
|
||||
This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths.
|
||||
///
|
||||
|
||||
We're using [Caddy](https://caddyserver.com/) here as reserve proxy to serve `wg-easy` on [https://wg-easy.example.com](https://wg-easy.example.com) via TLS.
|
||||
We're using [Caddy](https://caddyserver.com/) here as reverse proxy to serve `wg-easy` on [https://wg-easy.example.com](https://wg-easy.example.com) via TLS.
|
||||
|
||||
## Create a docker composition for `caddy`
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ To setup the IPv6 Network, simply run once:
|
||||
```shell
|
||||
docker network create \
|
||||
-d bridge --ipv6 \
|
||||
-d default \
|
||||
--subnet 10.42.42.0/24 \
|
||||
--subnet fdcc:ad94:bacf:61a3::/64 wg \
|
||||
--subnet fdcc:ad94:bacf:61a3::/64 \
|
||||
wg
|
||||
```
|
||||
|
||||
<!-- ref: major version -->
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Routed setup (No NAT)
|
||||
---
|
||||
|
||||
This guide shows how to run **wg-easy** with a routed setup, so packets are forwarded instead of NATed.
|
||||
|
||||
In a routed design, each WireGuard client keeps its own IPv4/IPv6 address. That means you can identify clients by their real addresses instead of seeing everything as the WireGuard server’s IP.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. You know how to add static routes on your router to the WireGuard server.
|
||||
|
||||
## Docker setup
|
||||
|
||||
To make use of our own IPv4/IPv6 addresses, run the container with the `network_mode: host` option.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wg-easy:
|
||||
image: ghcr.io/wg-easy/wg-easy:15
|
||||
container_name: wg-easy
|
||||
network_mode: 'host'
|
||||
volumes:
|
||||
- ./config:/etc/wireguard
|
||||
- /lib/modules:/lib/modules:ro
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Because we’re on the host network, remove any `ports:` and container `sysctls:` you might have had before.
|
||||
|
||||
## Kernel parameters (on the host)
|
||||
|
||||
With host networking, system sysctls must be set on the **host**. On your host, create `/etc/sysctl.d/90-wireguard.conf`:
|
||||
|
||||
```txt
|
||||
net.ipv4.ip_forward=1
|
||||
net.ipv4.conf.all.src_valid_mark=1
|
||||
net.ipv6.conf.all.disable_ipv6=0
|
||||
net.ipv6.conf.all.forwarding=1
|
||||
net.ipv6.conf.default.forwarding=1
|
||||
```
|
||||
|
||||
Apply and verify:
|
||||
|
||||
```shell
|
||||
sysctl -p /etc/sysctl.d/90-wireguard.conf
|
||||
sysctl -n net.ipv4.ip_forward # should print 1
|
||||
```
|
||||
|
||||
## Add static routes on your router
|
||||
|
||||
Pick an IPv4 and IPv6 subnet for your clients and add static routes on your router, pointing to the WireGuard server's LAN addresses.
|
||||
|
||||
### Example
|
||||
|
||||
/// note | 2001:db8::/32
|
||||
|
||||
The _documentation prefix_ `2001:db8::/32` (RFC 3849) used in this example is not meant for production use, replace it with your own ISP-assigned IPv6 prefix (GUA) or local prefix (ULA)
|
||||
///
|
||||
|
||||
I want my WireGuard clients in `192.168.0.0/24` and `2001:db8:abc:0::/64`.
|
||||
|
||||
- Routed IPv4 subnet: `192.168.0.0/24`
|
||||
- Routed IPv6 prefix: `2001:db8:abc:0::/64`
|
||||
- WireGuard server IPs: `192.168.10.118` and `2001:db8:abc:10:216:3eff:fedb:949e`
|
||||
|
||||
On your router:
|
||||
|
||||
- Route `192.168.0.0/24` → next hop `192.168.10.118`
|
||||
- Route `2001:db8:abc:0::/64` → next hop `2001:db8:abc:10:216:3eff:fedb:949e`
|
||||
|
||||
Don't forget to create the necessary firewall rules to allow these subnets to travel across your LAN. Some routers or servers may require specific Outbound NAT rules for the chosen IPv4 and IPv6 subnets to allow traffic to traverse your LAN.
|
||||
|
||||
## `wg-easy` configuration
|
||||
|
||||
In the Web UI → Admin → Interface, click Change CIDR and set the IPv4/IPv6 routed subnets you chose above. Save.
|
||||
|
||||
Then go to Admin → Hooks and add:
|
||||
|
||||
PostUp
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
@@ -144,7 +144,7 @@ sudo docker network create traefik
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
You can no access the Traefik dashboard at `https://traefik.$example.com$` with the credentials you set in `traefik_dynamic.yml`.
|
||||
You can now access the Traefik dashboard at `https://traefik.$example.com$` with the credentials you set in `traefik_dynamic.yml`.
|
||||
|
||||
## Add Labels to `wg-easy`
|
||||
|
||||
@@ -166,6 +166,7 @@ services:
|
||||
- "traefik.http.routers.wg-easy.entrypoints=websecure"
|
||||
- "traefik.http.routers.wg-easy.service=wg-easy"
|
||||
- "traefik.http.services.wg-easy.loadbalancer.server.port=51821"
|
||||
- "traefik.docker.network=traefik"
|
||||
...
|
||||
|
||||
networks:
|
||||
|
||||
@@ -38,6 +38,7 @@ If you're using podman, make sure to read the related [documentation][docs-podma
|
||||
To understand which tags you should use, read this section carefully. [Our CI][github-ci] will automatically build, test and push new images to the following container registry:
|
||||
|
||||
1. GitHub Container Registry ([`ghcr.io/wg-easy/wg-easy`][ghcr-image])
|
||||
2. Codeberg Container Registry ([`codeberg.org/wg-easy/wg-easy`][codeberg-image]) (IPv6 support)
|
||||
|
||||
All workflows are using the tagging convention listed below. It is subsequently applied to all images.
|
||||
|
||||
@@ -50,12 +51,13 @@ All workflows are using the tagging convention listed below. It is subsequently
|
||||
| `edge` | push to `master` | `ghcr.io/wg-easy/wg-easy:edge` | mostly unstable, gets frequent package and code updates |
|
||||
| `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs |
|
||||
|
||||
<!-- ref: major version -->
|
||||
<!-- ref: major version (check links too) -->
|
||||
|
||||
When publishing a tag we follow the [Semantic Versioning][semver] specification. The `latest` tag is always pointing to the latest stable release. If you want to avoid breaking changes, use the major version tag (e.g. `15`).
|
||||
|
||||
[github-ci]: https://github.com/wg-easy/wg-easy/actions
|
||||
[ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy
|
||||
[codeberg-image]: https://codeberg.org/wg-easy/-/packages/container/wg-easy/15
|
||||
[semver]: https://semver.org/
|
||||
|
||||
### Follow tutorials
|
||||
|
||||
+1
-1
@@ -12,5 +12,5 @@
|
||||
"devDependencies": {
|
||||
"prettier": "^3.6.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.12.4"
|
||||
"packageManager": "pnpm@10.21.0"
|
||||
}
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
folder="src/i18n/locales"
|
||||
base_file="$folder/en.json"
|
||||
|
||||
# Get all leaf keys from the English base file
|
||||
base_keys=$(jq -r 'paths(scalars) | map(tostring) | join(".")' "$base_file")
|
||||
total=$(echo "$base_keys" | wc -l)
|
||||
|
||||
# Loop through all JSON files in the folder
|
||||
for file in "$folder"/*.json; do
|
||||
name=$(basename "$file" .json)
|
||||
translated_keys=$(jq -r 'paths(scalars) | map(tostring) | join(".")' "$file")
|
||||
done=$(comm -12 <(echo "$base_keys" | sort) <(echo "$translated_keys" | sort) | wc -l)
|
||||
percent=$((100 * done / total))
|
||||
check="[ ]"
|
||||
[ "$percent" -eq 100 ] && check="[x]"
|
||||
printf "%s %s (%d%%)\n" "- $check" "$name" "$percent"
|
||||
done
|
||||
@@ -11,10 +11,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { VueApexChartsComponent } from 'vue3-apexcharts';
|
||||
import type { VueApexChartsComponentProps } from 'vue3-apexcharts';
|
||||
|
||||
defineProps<{
|
||||
options: VueApexChartsComponent['options'];
|
||||
series: VueApexChartsComponent['series'];
|
||||
options: VueApexChartsComponentProps['options'];
|
||||
series: VueApexChartsComponentProps['series'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="relative w-60 md:mr-2">
|
||||
<div class="relative flex h-full items-center">
|
||||
<MagnifyingGlassIcon
|
||||
class="absolute left-2.5 h-4 w-4 text-gray-400 dark:text-neutral-500"
|
||||
/>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
:placeholder="$t('client.search')"
|
||||
class="w-full rounded bg-white py-2 pr-8 text-sm text-gray-900 shadow-sm ring-1 ring-gray-300 transition-all placeholder:text-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-red-600 dark:bg-neutral-800 dark:text-white dark:ring-neutral-700 dark:placeholder:text-neutral-500 dark:focus:ring-red-700"
|
||||
@input="updateSearch"
|
||||
/>
|
||||
<button
|
||||
v-if="searchQuery"
|
||||
class="absolute right-2 flex h-5 w-5 items-center justify-center rounded-full bg-gray-200 text-gray-600 hover:bg-gray-300 hover:text-gray-800 dark:bg-neutral-700 dark:text-neutral-300 dark:hover:bg-neutral-600 dark:hover:text-neutral-100"
|
||||
aria-label="Clear search"
|
||||
@click="clearSearch"
|
||||
>
|
||||
<IconsClose class="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const clientsStore = useClientsStore();
|
||||
const searchQuery = ref('');
|
||||
|
||||
const updateSearch = useDebounceFn(() => {
|
||||
clientsStore.setSearchQuery(searchQuery.value);
|
||||
}, 300);
|
||||
|
||||
function clearSearch() {
|
||||
searchQuery.value = '';
|
||||
clientsStore.setSearchQuery('');
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,12 @@
|
||||
<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>
|
||||
@@ -35,7 +42,11 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
const data = defineModel<string[]>();
|
||||
defineProps<{ emptyText?: string[]; name: string }>();
|
||||
defineProps<{
|
||||
emptyText?: string[];
|
||||
name: string;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
|
||||
function update(e: Event, i: number) {
|
||||
const v = (e.target as HTMLInputElement).value;
|
||||
|
||||
@@ -6,6 +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>
|
||||
<div class="flex gap-1">
|
||||
<BaseInput
|
||||
@@ -21,7 +27,9 @@
|
||||
<BasePrimaryButton as="span">
|
||||
<div class="flex items-center gap-3">
|
||||
<IconsSparkles class="w-4" />
|
||||
<span>{{ $t('admin.config.suggest') }}</span>
|
||||
<span class="whitespace-nowrap">
|
||||
{{ $t('admin.config.suggest') }}
|
||||
</span>
|
||||
</div>
|
||||
</BasePrimaryButton>
|
||||
</AdminSuggestDialog>
|
||||
@@ -36,6 +44,7 @@ defineProps<{
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
url: '/api/admin/ip-info' | '/api/setup/4';
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
|
||||
const data = defineModel<string | null>({
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<FormLabel :for="id">
|
||||
{{ label }}
|
||||
</FormLabel>
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<span :id="id" class="flex flex-col justify-center">{{ data }}</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
data?: string;
|
||||
}>();
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<FormLabel :for="id">
|
||||
{{ label }}
|
||||
</FormLabel>
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</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 }>();
|
||||
|
||||
const data = defineModel<number | null>({
|
||||
set(value) {
|
||||
const temp = value ?? null;
|
||||
if (temp === 0) {
|
||||
return null;
|
||||
}
|
||||
if ((temp as string | null) === '') {
|
||||
return null;
|
||||
}
|
||||
return temp;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -6,6 +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"
|
||||
@@ -24,6 +30,7 @@ defineProps<{
|
||||
description?: string;
|
||||
autocomplete?: string;
|
||||
placeholder?: string;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
|
||||
const data = defineModel<string | null>({
|
||||
|
||||
@@ -6,12 +6,23 @@
|
||||
<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 }>();
|
||||
defineProps<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
|
||||
const data = defineModel<number>();
|
||||
</script>
|
||||
|
||||
@@ -6,11 +6,22 @@
|
||||
<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 }>();
|
||||
defineProps<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
const data = defineModel<boolean>();
|
||||
</script>
|
||||
|
||||
@@ -6,6 +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"
|
||||
@@ -24,6 +30,7 @@ defineProps<{
|
||||
description?: string;
|
||||
autocomplete?: string;
|
||||
disabled?: boolean;
|
||||
overridden?: boolean;
|
||||
}>();
|
||||
|
||||
const data = defineModel<string>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-shrink-0 space-x-1 md:block">
|
||||
<div class="flex flex-shrink-0 items-center space-x-2">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -44,18 +44,19 @@ const { t } = useI18n();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const menuItems = [
|
||||
const menuItems = computed(() => [
|
||||
{ id: 'general', name: t('pages.admin.general') },
|
||||
{ id: 'config', name: t('pages.admin.config') },
|
||||
{ id: 'interface', name: t('pages.admin.interface') },
|
||||
{ id: 'hooks', name: t('pages.admin.hooks') },
|
||||
];
|
||||
]);
|
||||
|
||||
const defaultItem = { id: '', name: t('pages.admin.panel') };
|
||||
|
||||
const activeMenuItem = computed(() => {
|
||||
return (
|
||||
menuItems.find((item) => route.path === `/admin/${item.id}`) ?? defaultItem
|
||||
menuItems.value.find((item) => route.path === `/admin/${item.id}`) ??
|
||||
defaultItem
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
: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>
|
||||
@@ -24,13 +26,18 @@
|
||||
<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" />
|
||||
<FormArrayField
|
||||
v-model="data.defaultDns"
|
||||
name="defaultDns"
|
||||
:overridden="overrides?.defaultDns"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading>
|
||||
@@ -39,12 +46,69 @@
|
||||
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">
|
||||
<FormHeading>{{ $t('awg.obfuscationParameters') }}</FormHeading>
|
||||
|
||||
<FormNullNumberField
|
||||
id="jC"
|
||||
v-model="data.defaultJC"
|
||||
:label="$t('awg.jCLabel')"
|
||||
:description="$t('awg.jCDescription')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="jMin"
|
||||
v-model="data.defaultJMin"
|
||||
:label="$t('awg.jMinLabel')"
|
||||
:description="$t('awg.jMinDescription')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="jMax"
|
||||
v-model="data.defaultJMax"
|
||||
:label="$t('awg.jMaxLabel')"
|
||||
:description="$t('awg.jMaxDescription')"
|
||||
/>
|
||||
|
||||
<div class="col-span-full text-sm">* {{ $t('awg.mtuNote') }}</div>
|
||||
|
||||
<FormNullTextField
|
||||
id="i1"
|
||||
v-model="data.defaultI1"
|
||||
:label="$t('awg.i1Label')"
|
||||
:description="$t('awg.i1Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i2"
|
||||
v-model="data.defaultI2"
|
||||
:label="$t('awg.i2Label')"
|
||||
:description="$t('awg.i2Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i3"
|
||||
v-model="data.defaultI3"
|
||||
:label="$t('awg.i3Label')"
|
||||
:description="$t('awg.i3Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i4"
|
||||
v-model="data.defaultI4"
|
||||
:label="$t('awg.i4Label')"
|
||||
:description="$t('awg.i4Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i5"
|
||||
v-model="data.defaultI5"
|
||||
:label="$t('awg.i5Label')"
|
||||
:description="$t('awg.i5Description')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
@@ -57,10 +121,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const globalStore = useGlobalStore();
|
||||
|
||||
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,6 +7,7 @@
|
||||
v-model="data.sessionTimeout"
|
||||
:label="$t('admin.general.sessionTimeout')"
|
||||
:description="$t('admin.general.sessionTimeoutDesc')"
|
||||
:overridden="overrides?.sessionTimeout"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
@@ -16,18 +17,21 @@
|
||||
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>
|
||||
@@ -43,6 +47,13 @@
|
||||
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,21 +6,25 @@
|
||||
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>
|
||||
@@ -37,6 +41,12 @@ 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,18 +7,124 @@
|
||||
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">
|
||||
<FormHeading>{{ $t('awg.obfuscationParameters') }}</FormHeading>
|
||||
|
||||
<FormNullNumberField
|
||||
id="jC"
|
||||
v-model="data.jC"
|
||||
:label="$t('awg.jCLabel')"
|
||||
:description="$t('awg.jCDescription')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="jMin"
|
||||
v-model="data.jMin"
|
||||
:label="$t('awg.jMinLabel')"
|
||||
:description="$t('awg.jMinDescription')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="jMax"
|
||||
v-model="data.jMax"
|
||||
:label="$t('awg.jMaxLabel')"
|
||||
:description="$t('awg.jMaxDescription')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="s1"
|
||||
v-model="data.s1"
|
||||
:label="$t('awg.s1Label')"
|
||||
:description="$t('awg.s1Description')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="s2"
|
||||
v-model="data.s2"
|
||||
:label="$t('awg.s2Label')"
|
||||
:description="$t('awg.s2Description')"
|
||||
/>
|
||||
|
||||
<div class="col-span-full text-sm">* {{ $t('awg.mtuNote') }}</div>
|
||||
|
||||
<FormNullNumberField
|
||||
id="s3"
|
||||
v-model="data.s3"
|
||||
:label="$t('awg.s3Label')"
|
||||
:description="$t('awg.s3Description')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="s4"
|
||||
v-model="data.s4"
|
||||
:label="$t('awg.s4Label')"
|
||||
:description="$t('awg.s4Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i1"
|
||||
v-model="data.i1"
|
||||
:label="$t('awg.i1Label')"
|
||||
:description="$t('awg.i1Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i2"
|
||||
v-model="data.i2"
|
||||
:label="$t('awg.i2Label')"
|
||||
:description="$t('awg.i2Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i3"
|
||||
v-model="data.i3"
|
||||
:label="$t('awg.i3Label')"
|
||||
:description="$t('awg.i3Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i4"
|
||||
v-model="data.i4"
|
||||
:label="$t('awg.i4Label')"
|
||||
:description="$t('awg.i4Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i5"
|
||||
v-model="data.i5"
|
||||
:label="$t('awg.i5Label')"
|
||||
:description="$t('awg.i5Description')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="h1"
|
||||
v-model="data.h1"
|
||||
:label="$t('awg.h1Label')"
|
||||
:description="$t('awg.h1Description')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="h2"
|
||||
v-model="data.h2"
|
||||
:label="$t('awg.h2Label')"
|
||||
:description="$t('awg.h2Description')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="h3"
|
||||
v-model="data.h3"
|
||||
:label="$t('awg.h3Label')"
|
||||
:description="$t('awg.h3Description')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="h4"
|
||||
v-model="data.h4"
|
||||
:label="$t('awg.h4Label')"
|
||||
:description="$t('awg.h4Description')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
@@ -53,12 +159,20 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const globalStore = useGlobalStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
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(
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
v-model="data.ipv6Address"
|
||||
label="IPv6"
|
||||
/>
|
||||
<FormInfoField
|
||||
id="endpoint"
|
||||
:data="data.endpoint ?? $t('client.notConnected')"
|
||||
:label="$t('client.endpoint')"
|
||||
:description="$t('client.endpointDesc')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading :description="$t('client.allowedIpsDesc')">
|
||||
@@ -76,6 +82,61 @@
|
||||
:label="$t('general.persistentKeepalive')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup v-if="globalStore.information?.isAwg">
|
||||
<FormHeading>{{ $t('awg.obfuscationParameters') }}</FormHeading>
|
||||
|
||||
<FormNullNumberField
|
||||
id="jC"
|
||||
v-model="data.jC"
|
||||
:label="$t('awg.jCLabel')"
|
||||
:description="$t('awg.jCDescription')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="Jmin"
|
||||
v-model="data.jMin"
|
||||
:label="$t('awg.jMinLabel')"
|
||||
:description="$t('awg.jMinDescription')"
|
||||
/>
|
||||
<FormNullNumberField
|
||||
id="Jmax"
|
||||
v-model="data.jMax"
|
||||
:label="$t('awg.jMaxLabel')"
|
||||
:description="$t('awg.jMaxDescription')"
|
||||
/>
|
||||
|
||||
<div class="col-span-full text-sm">* {{ $t('awg.mtuNote') }}</div>
|
||||
|
||||
<FormNullTextField
|
||||
id="i1"
|
||||
v-model="data.i1"
|
||||
:label="$t('awg.i1Label')"
|
||||
:description="$t('awg.i1Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i2"
|
||||
v-model="data.i2"
|
||||
:label="$t('awg.i2Label')"
|
||||
:description="$t('awg.i2Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i3"
|
||||
v-model="data.i3"
|
||||
:label="$t('awg.i3Label')"
|
||||
:description="$t('awg.i3Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i4"
|
||||
v-model="data.i4"
|
||||
:label="$t('awg.i4Label')"
|
||||
:description="$t('awg.i4Description')"
|
||||
/>
|
||||
<FormNullTextField
|
||||
id="i5"
|
||||
v-model="data.i5"
|
||||
:label="$t('awg.i5Label')"
|
||||
:description="$t('awg.i5Description')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading :description="$t('client.hooksDescription')">
|
||||
{{ $t('client.hooks') }}
|
||||
@@ -134,6 +195,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
const authStore = useAuthStore();
|
||||
const globalStore = useGlobalStore();
|
||||
authStore.update();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<PanelHead>
|
||||
<PanelHeadTitle :text="$t('pages.clients')" />
|
||||
<PanelHeadBoat>
|
||||
<ClientsSearch />
|
||||
<ClientsSort />
|
||||
<ClientsNew />
|
||||
</PanelHeadBoat>
|
||||
|
||||
@@ -55,10 +55,16 @@ const _submit = useSubmit(
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
revert: async (success) => {
|
||||
revert: async (success, data) => {
|
||||
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');
|
||||
}
|
||||
}
|
||||
},
|
||||
noSuccessToast: true,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import VueApexCharts from 'vue3-apexcharts';
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.use(VueApexCharts);
|
||||
// https://github.com/apexcharts/vue3-apexcharts/issues/141
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
nuxtApp.vueApp.use(VueApexCharts as any);
|
||||
});
|
||||
|
||||
@@ -31,8 +31,13 @@ export const useClientsStore = defineStore('Clients', () => {
|
||||
const clients = ref<null | LocalClient[]>(null);
|
||||
const clientsPersist = ref<Record<string, ClientPersist>>({});
|
||||
|
||||
const searchParams = ref({
|
||||
filter: undefined as string | undefined,
|
||||
});
|
||||
|
||||
const { data: _clients, refresh: _refresh } = useFetch('/api/client', {
|
||||
method: 'get',
|
||||
params: searchParams,
|
||||
});
|
||||
|
||||
// TODO: rewrite
|
||||
@@ -120,6 +125,7 @@ export const useClientsStore = defineStore('Clients', () => {
|
||||
};
|
||||
});
|
||||
|
||||
// TODO: move sort to backend
|
||||
if (transformedClients !== undefined) {
|
||||
transformedClients = sortByProperty(
|
||||
transformedClients,
|
||||
@@ -130,5 +136,11 @@ export const useClientsStore = defineStore('Clients', () => {
|
||||
|
||||
clients.value = transformedClients ?? null;
|
||||
}
|
||||
return { clients, clientsPersist, refresh, _clients };
|
||||
|
||||
function setSearchQuery(filter: string) {
|
||||
clients.value = null;
|
||||
searchParams.value.filter = filter || undefined;
|
||||
}
|
||||
|
||||
return { clients, clientsPersist, refresh, _clients, setSearchQuery };
|
||||
});
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
import en from './locales/en.json';
|
||||
import pl from './locales/pl.json';
|
||||
import uk from './locales/uk.json';
|
||||
import fr from './locales/fr.json';
|
||||
import de from './locales/de.json';
|
||||
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 ko from './locales/ko.json';
|
||||
import es from './locales/es.json';
|
||||
import ptbr from './locales/pt-BR.json';
|
||||
import tr from './locales/tr.json';
|
||||
import bn from './locales/bn.json';
|
||||
import id from './locales/id.json';
|
||||
|
||||
export default defineI18nConfig(() => ({
|
||||
legacy: false,
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
en,
|
||||
pl,
|
||||
uk,
|
||||
fr,
|
||||
de,
|
||||
it,
|
||||
ru,
|
||||
'zh-HK': zhhk,
|
||||
'zh-CN': zhcn,
|
||||
ko,
|
||||
es,
|
||||
'pt-BR': ptbr,
|
||||
tr,
|
||||
bn,
|
||||
id,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "অ্যাকাউন্ট",
|
||||
"clients": "ক্লায়েন্টস",
|
||||
"admin": {
|
||||
"panel": "অ্যাডমিন প্যানেল",
|
||||
"general": "সাধারণ",
|
||||
"config": "কনফিগ",
|
||||
"interface": "ইন্টারফেস",
|
||||
"hooks": "হুকস"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "ই-মেইল"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "বর্তমান পাসওয়ার্ড",
|
||||
"enable2fa": "দুই স্তরের যাচাইকরণ চালু করুন",
|
||||
"enable2faDesc": "আপনার অথেন্টিকেটর অ্যাপ দিয়ে QR কোড স্ক্যান করুন অথবা কীটি ম্যানুয়ালি প্রবেশ করুন।",
|
||||
"2faKey": "TOTP কী",
|
||||
"2faCodeDesc": "আপনার অথেন্টিকেটর অ্যাপ থেকে কোডটি প্রবেশ করুন।",
|
||||
"disable2fa": "দুই স্তরের যাচাইকরণ বন্ধ করুন",
|
||||
"disable2faDesc": "দুই স্তরের যাচাইকরণ বন্ধ করতে আপনার পাসওয়ার্ড দিন।"
|
||||
},
|
||||
"general": {
|
||||
"name": "নাম",
|
||||
"username": "ইউজারনেম",
|
||||
"password": "পাসওয়ার্ড",
|
||||
"newPassword": "নতুন পাসওয়ার্ড",
|
||||
"updatePassword": "পাসওয়ার্ড আপডেট করুন",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "অনুমোদিত IPs",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "স্থায়ী কিপ-এলাইভ",
|
||||
"logout": "লগআউট",
|
||||
"continue": "চালিয়ে যান",
|
||||
"host": "হোস্ট",
|
||||
"port": "পোর্ট",
|
||||
"yes": "হ্যাঁ",
|
||||
"no": "না",
|
||||
"confirmPassword": "পাসওয়ার্ড নিশ্চিত করুন",
|
||||
"loading": "লোড হচ্ছে...",
|
||||
"2fa": "দুই স্তরের যাচাইকরণ",
|
||||
"2faCode": "TOTP কোড"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "wg-easy এর প্রথম সেটআপে স্বাগতম",
|
||||
"welcomeDesc": "আপনি 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": "যেদিন ক্লায়েন্ট নিষ্ক্রিয় হবে। স্থায়ী করতে ফাঁকা রাখুন",
|
||||
"deleteClient": "ক্লায়েন্ট মুছে ফেলুন",
|
||||
"deleteDialog1": "আপনি কি নিশ্চিতভাবে মুছে ফেলতে চান",
|
||||
"deleteDialog2": "এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না।",
|
||||
"enabled": "সক্রিয়",
|
||||
"address": "ঠিকানা",
|
||||
"serverAllowedIps": "সার্ভার অনুমোদিত IPs",
|
||||
"otlDesc": "সংক্ষিপ্ত একবারের লিঙ্ক তৈরি করুন",
|
||||
"permanent": "স্থায়ী",
|
||||
"createdOn": "তৈরি হয়েছে",
|
||||
"lastSeen": "শেষ দেখা হয়েছে",
|
||||
"totalDownload": "মোট ডাউনলোড: ",
|
||||
"totalUpload": "মোট আপলোড: ",
|
||||
"newClient": "নতুন ক্লায়েন্ট",
|
||||
"disableClient": "ক্লায়েন্ট নিষ্ক্রিয় করুন",
|
||||
"enableClient": "ক্লায়েন্ট সক্রিয় করুন",
|
||||
"noPrivKey": "এই ক্লায়েন্টের কোনো ব্যক্তিগত কী নেই। কনফিগারেশন তৈরি করা যাবে না।",
|
||||
"showQR": "QR কোড দেখান",
|
||||
"downloadConfig": "কনফিগারেশন ডাউনলোড করুন",
|
||||
"allowedIpsDesc": "কোন IPs VPN এর মাধ্যমে রাউট হবে (গ্লোবাল কনফিগ ওভাররাইড করবে)",
|
||||
"serverAllowedIpsDesc": "কোন IPs সার্ভার ক্লায়েন্টে রাউট করবে",
|
||||
"mtuDesc": "VPN টানেলের জন্য সর্বাধিক প্যাকেট সাইজ নির্ধারণ করে",
|
||||
"persistentKeepaliveDesc": "কিপ-এলাইভ প্যাকেটের জন্য ইন্টারভাল (সেকেন্ডে)। 0 হলে বন্ধ",
|
||||
"hooks": "হুকস",
|
||||
"hooksDescription": "হুকস শুধুমাত্র wg-quick এর সাথে কাজ করে",
|
||||
"hooksLeaveEmpty": "শুধুমাত্র wg-quick এর জন্য। অন্যথায় ফাঁকা রাখুন",
|
||||
"dnsDesc": "DNS সার্ভার ক্লায়েন্টরা ব্যবহার করবে (গ্লোবাল কনফিগ ওভাররাইড করবে)",
|
||||
"notConnected": "ক্লায়েন্ট সংযুক্ত নয়",
|
||||
"endpoint": "এন্ডপয়েন্ট",
|
||||
"endpointDesc": "ক্লায়েন্টের IP যেখান থেকে WireGuard সংযোগ স্থাপন করা হয়েছে"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "পরিবর্তন করুন",
|
||||
"cancel": "বাতিল করুন",
|
||||
"create": "তৈরি করুন"
|
||||
},
|
||||
"toast": {
|
||||
"success": "সফলতা",
|
||||
"saved": "সংরক্ষিত",
|
||||
"error": "ত্রুটি"
|
||||
},
|
||||
"form": {
|
||||
"actions": "কর্মসমূহ",
|
||||
"save": "সংরক্ষণ করুন",
|
||||
"revert": "পূর্বাবস্থায় ফেরান",
|
||||
"sectionGeneral": "সাধারণ",
|
||||
"sectionAdvanced": "উন্নত",
|
||||
"noItems": "কোনো আইটেম নেই",
|
||||
"nullNoItems": "কোনো আইটেম নেই। গ্লোবাল কনফিগ ব্যবহার হচ্ছে",
|
||||
"add": "যোগ করুন"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "সেশন টাইমআউট",
|
||||
"sessionTimeoutDesc": "মনে রাখুনের জন্য সেশন সময়কাল (সেকেন্ডে)",
|
||||
"metrics": "মেট্রিক্স",
|
||||
"metricsPassword": "পাসওয়ার্ড",
|
||||
"metricsPasswordDesc": "মেট্রিক্স এন্ডপয়েন্টের জন্য বেয়ারার পাসওয়ার্ড (পাসওয়ার্ড বা argon2 হ্যাশ)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "JSON ফরম্যাটে মেট্রিক্সের রুট",
|
||||
"prometheus": "Prometheus",
|
||||
"prometheusDesc": "Prometheus মেট্রিক্সের রুট"
|
||||
},
|
||||
"config": {
|
||||
"connection": "সংযোগ",
|
||||
"hostDesc": "পাবলিক হোস্টনেম ক্লায়েন্টরা সংযোগ করবে (কনফিগ বাতিল হবে)",
|
||||
"portDesc": "পাবলিক UDP পোর্ট ক্লায়েন্টরা সংযোগ করবে (কনফিগ বাতিল হবে, আপনি সম্ভবত ইন্টারফেস পোর্টও পরিবর্তন করতে চাইবেন)",
|
||||
"allowedIpsDesc": "অনুমোদিত IPs ক্লায়েন্টরা ব্যবহার করবে (গ্লোবাল কনফিগ)",
|
||||
"dnsDesc": "DNS সার্ভার ক্লায়েন্টরা ব্যবহার করবে (গ্লোবাল কনফিগ)",
|
||||
"mtuDesc": "MTU ক্লায়েন্টরা ব্যবহার করবে (শুধুমাত্র নতুন ক্লায়েন্টের জন্য)",
|
||||
"persistentKeepaliveDesc": "সার্ভারে কিপ-এলাইভ পাঠানোর ইন্টারভাল (সেকেন্ডে)। 0 = বন্ধ (শুধুমাত্র নতুন ক্লায়েন্টের জন্য)",
|
||||
"suggest": "পরামর্শ",
|
||||
"suggestDesc": "হোস্ট ফিল্ডের জন্য একটি IP-ঠিকানা বা হোস্টনেম নির্বাচন করুন"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "CIDR পরিবর্তন হয়েছে",
|
||||
"device": "ডিভাইস",
|
||||
"deviceDesc": "ইথারনেট ডিভাইস যার মাধ্যমে wireguard ট্রাফিক ফরওয়ার্ড হবে",
|
||||
"mtuDesc": "WireGuard ব্যবহার করবে এমন MTU",
|
||||
"portDesc": "UDP পোর্ট WireGuard শুনবে (আপনি সম্ভবত কনফিগ পোর্টও পরিবর্তন করতে চাইবেন)",
|
||||
"changeCidr": "CIDR পরিবর্তন করুন",
|
||||
"restart": "ইন্টারফেস রিস্টার্ট করুন",
|
||||
"restartDesc": "WireGuard ইন্টারফেস রিস্টার্ট করুন",
|
||||
"restartWarn": "আপনি কি নিশ্চিতভাবে ইন্টারফেস রিস্টার্ট করতে চান? এতে সব ক্লায়েন্ট সংযোগ বিচ্ছিন্ন হবে।",
|
||||
"restartSuccess": "ইন্টারফেস রিস্টার্ট হয়েছে"
|
||||
},
|
||||
"introText": "অ্যাডমিন প্যানেলে স্বাগতম।\n\nএখানে আপনি সাধারণ সেটিংস, কনফিগারেশন, ইন্টারফেস সেটিংস এবং হুকস পরিচালনা করতে পারবেন।\n\nসাইডবার থেকে একটি বিভাগ নির্বাচন করে শুরু করুন।"
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
"required": "{0} আবশ্যক",
|
||||
"validNumber": "{0} একটি বৈধ সংখ্যা হতে হবে",
|
||||
"validString": "{0} একটি বৈধ স্ট্রিং হতে হবে",
|
||||
"validBoolean": "{0} একটি বৈধ বুলিয়ান হতে হবে",
|
||||
"validArray": "{0} একটি বৈধ অ্যারে হতে হবে",
|
||||
"stringMin": "{0} কমপক্ষে {1} অক্ষর হতে হবে",
|
||||
"numberMin": "{0} কমপক্ষে {1} হতে হবে"
|
||||
},
|
||||
"client": {
|
||||
"id": "ক্লায়েন্ট আইডি",
|
||||
"name": "নাম",
|
||||
"expiresAt": "মেয়াদ শেষ",
|
||||
"address4": "IPv4 ঠিকানা",
|
||||
"address6": "IPv6 ঠিকানা",
|
||||
"serverAllowedIps": "সার্ভার অনুমোদিত IPs"
|
||||
},
|
||||
"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": "বডি একটি বৈধ অবজেক্ট হতে হবে",
|
||||
"hook": "হুক",
|
||||
"enabled": "সক্রিয়",
|
||||
"mtu": "MTU",
|
||||
"port": "পোর্ট",
|
||||
"persistentKeepalive": "স্থায়ী কিপ-এলাইভ",
|
||||
"address": "IP ঠিকানা",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "অনুমোদিত IPs",
|
||||
"file": "ফাইল"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,11 @@
|
||||
"hooks": "Hooks",
|
||||
"hooksDescription": "Hooks only work with wg-quick",
|
||||
"hooksLeaveEmpty": "Only for wg-quick. Otherwise, leave it empty",
|
||||
"dnsDesc": "DNS server clients will use (overrides global config)"
|
||||
"dnsDesc": "DNS server clients will use (overrides global config)",
|
||||
"notConnected": "Client not connected",
|
||||
"endpoint": "Endpoint",
|
||||
"endpointDesc": "IP of the client from which the WireGuard connection is established",
|
||||
"search": "Search clients..."
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Change",
|
||||
@@ -233,5 +237,41 @@
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
},
|
||||
"awg": {
|
||||
"jCLabel": "Junk packet count (Jc)",
|
||||
"jCDescription": "Number of junk packets to send (1-128, recommended: 4-12)",
|
||||
"jMinLabel": "Junk packet min size (Jmin)",
|
||||
"jMinDescription": "Minimum size of junk packets (0-1279*, recommended: 8, must be < Jmax)",
|
||||
"jMaxLabel": "Junk packet max size (Jmax)",
|
||||
"jMaxDescription": "Maximum size of junk packets (1-1280*, recommended: 80, must be > Jmin)",
|
||||
"s1Label": "Init packet junk size (S1)",
|
||||
"s1Description": "Init packet junk size (0-1132[1280* - 148 = 1132], recommended: 15-150, S1+56 ≠ S2)",
|
||||
"s2Label": "Response packet junk size (S2)",
|
||||
"s2Description": "Response packet junk size (0-1188[1280* - 92 = 1188], recommended: 15-150)",
|
||||
"s3Label": "Cookie reply packet junk size (S3)",
|
||||
"s3Description": "Cookie reply packet junk size",
|
||||
"s4Label": "Transport packet junk size (S4)",
|
||||
"s4Description": "Transport packet junk size",
|
||||
"i1Label": "Special junk packet 1 (I1)",
|
||||
"i1Description": "Protocol mimic packet in hex format: <b 0x...>",
|
||||
"i2Label": "Special junk packet 2 (I2)",
|
||||
"i2Description": "Protocol mimic packet in hex format: <b 0x...>",
|
||||
"i3Label": "Special junk packet 3 (I3)",
|
||||
"i3Description": "Protocol mimic packet in hex format: <b 0x...>",
|
||||
"i4Label": "Special junk packet 4 (I4)",
|
||||
"i4Description": "Protocol mimic packet in hex format: <b 0x...>",
|
||||
"i5Label": "Special junk packet 5 (I5)",
|
||||
"i5Description": "Protocol mimic packet in hex format: <b 0x...>",
|
||||
"h1Label": "Init magic header (H1)",
|
||||
"h1Description": "Init packet header value (5-2147483647, must be unique from H2-H4)",
|
||||
"h2Label": "Response magic header (H2)",
|
||||
"h2Description": "Response packet header value (5-2147483647, must be unique from H1, H3, H4)",
|
||||
"h3Label": "Cookie reply magic header (H3)",
|
||||
"h3Description": "Cookie reply packet header value (5-2147483647, must be unique from H1, H2, H4)",
|
||||
"h4Label": "Transport magic header (H4)",
|
||||
"h4Description": "Transport packet header value (5-2147483647, must be unique from H1-H3)",
|
||||
"mtuNote": "Values depend on the MTU",
|
||||
"obfuscationParameters": "AmneziaWG Obfuscation Parameters"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "Cuenta",
|
||||
"clients": "Clientes",
|
||||
"admin": {
|
||||
"panel": "Panel de Admin.",
|
||||
"general": "General",
|
||||
"config": "Configuración",
|
||||
"interface": "Interfaz",
|
||||
"hooks": "Hooks"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "Correo electrónico"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "Contraseña actual",
|
||||
"enable2fa": "Habilitar autenticación en dos pasos",
|
||||
"enable2faDesc": "Escanea el código QR con tu aplicación de autenticación o introduce la clave manualmente.",
|
||||
"2faKey": "Clave TOTP",
|
||||
"2faCodeDesc": "Introduce el código de tu aplicación de autenticación.",
|
||||
"disable2fa": "Desactivar autenticación en dos pasos",
|
||||
"disable2faDesc": "Introduce tu contraseña para desactivar la autenticación en dos pasos."
|
||||
},
|
||||
"general": {
|
||||
"name": "Nombre",
|
||||
"username": "Usuario",
|
||||
"password": "Contraseña",
|
||||
"newPassword": "Nueva contraseña",
|
||||
"updatePassword": "Actualizar contraseña",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "IPs permitidas",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "Keepalive persistente",
|
||||
"logout": "Cerrar sesión",
|
||||
"continue": "Continuar",
|
||||
"host": "Host",
|
||||
"port": "Puerto",
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
"confirmPassword": "Confirmar contraseña",
|
||||
"loading": "Cargando...",
|
||||
"2fa": "Autenticación en dos pasos",
|
||||
"2faCode": "Código TOTP"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "Bienvenido a tu primera configuración de wg-easy",
|
||||
"welcomeDesc": "Has encontrado la forma más fácil de instalar y gestionar WireGuard en cualquier host Linux",
|
||||
"existingSetup": "¿Tienes una configuración existente?",
|
||||
"createAdminDesc": "Por favor, introduce un nombre de usuario y una contraseña segura. Esta información se usará para iniciar sesión en el panel de administración.",
|
||||
"setupConfigDesc": "Introduce la información del host y puerto. Se utilizará para la configuración del cliente al instalar WireGuard en sus dispositivos.",
|
||||
"setupMigrationDesc": "Proporciona el archivo de copia de seguridad si deseas migrar tus datos desde una versión anterior de wg-easy a esta nueva configuración.",
|
||||
"upload": "Subir",
|
||||
"migration": "Restaurar copia de seguridad:",
|
||||
"createAccount": "Crear cuenta",
|
||||
"successful": "Configuración completada",
|
||||
"hostDesc": "Nombre de host público al que se conectarán los clientes",
|
||||
"portDesc": "Puerto UDP público al que se conectarán los clientes y que escuchará WireGuard"
|
||||
},
|
||||
"update": {
|
||||
"updateAvailable": "¡Hay una actualización disponible!",
|
||||
"update": "Actualizar"
|
||||
},
|
||||
"theme": {
|
||||
"dark": "Tema oscuro",
|
||||
"light": "Tema claro",
|
||||
"system": "Tema del sistema"
|
||||
},
|
||||
"layout": {
|
||||
"toggleCharts": "Mostrar/ocultar gráficos",
|
||||
"donate": "Donar"
|
||||
},
|
||||
"login": {
|
||||
"signIn": "Iniciar sesión",
|
||||
"rememberMe": "Recordarme",
|
||||
"rememberMeDesc": "Mantener sesión iniciada tras cerrar el navegador",
|
||||
"insecure": "No puedes iniciar sesión con una conexión no segura. Usa HTTPS.",
|
||||
"2faRequired": "Se requiere autenticación en dos pasos",
|
||||
"2faWrong": "El código de autenticación en dos pasos es incorrecto"
|
||||
},
|
||||
"client": {
|
||||
"empty": "No hay clientes todavía.",
|
||||
"newShort": "Nuevo",
|
||||
"sort": "Ordenar",
|
||||
"create": "Crear cliente",
|
||||
"created": "Cliente creado",
|
||||
"new": "Nuevo cliente",
|
||||
"name": "Nombre",
|
||||
"expireDate": "Fecha de expiración",
|
||||
"expireDateDesc": "Fecha en que el cliente será desactivado. En blanco = permanente",
|
||||
"deleteClient": "Eliminar cliente",
|
||||
"deleteDialog1": "¿Estás seguro de que deseas eliminar a",
|
||||
"deleteDialog2": "Esta acción no se puede deshacer.",
|
||||
"enabled": "Habilitado",
|
||||
"address": "Dirección",
|
||||
"serverAllowedIps": "IPs permitidas del servidor",
|
||||
"otlDesc": "Generar enlace único de un solo uso",
|
||||
"permanent": "Permanente",
|
||||
"createdOn": "Creado el ",
|
||||
"lastSeen": "Última conexión el ",
|
||||
"totalDownload": "Descarga total: ",
|
||||
"totalUpload": "Subida total: ",
|
||||
"newClient": "Nuevo cliente",
|
||||
"disableClient": "Desactivar cliente",
|
||||
"enableClient": "Activar cliente",
|
||||
"noPrivKey": "Este cliente no tiene clave privada conocida. No se puede crear la configuración.",
|
||||
"showQR": "Mostrar código QR",
|
||||
"downloadConfig": "Descargar configuración",
|
||||
"allowedIpsDesc": "Qué IPs se enrutarán por la VPN (anula la configuración global)",
|
||||
"serverAllowedIpsDesc": "Qué IPs el servidor enviará al cliente",
|
||||
"mtuDesc": "Tamaño máximo de paquete (MTU) para el túnel VPN",
|
||||
"persistentKeepaliveDesc": "Intervalo (en segundos) para los paquetes keep-alive. 0 lo desactiva",
|
||||
"hooks": "Hooks",
|
||||
"hooksDescription": "Los hooks solo funcionan con wg-quick",
|
||||
"hooksLeaveEmpty": "Solo para wg-quick. En caso contrario, dejar vacío",
|
||||
"dnsDesc": "Servidor DNS que usarán los clientes (anula la configuración global)",
|
||||
"notConnected": "Cliente no conectado",
|
||||
"endpoint": "Punto de conexión",
|
||||
"endpointDesc": "IP del cliente desde donde se establece la conexión WireGuard"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Cambiar",
|
||||
"cancel": "Cancelar",
|
||||
"create": "Crear"
|
||||
},
|
||||
"toast": {
|
||||
"success": "Éxito",
|
||||
"saved": "Guardado",
|
||||
"error": "Error"
|
||||
},
|
||||
"form": {
|
||||
"actions": "Acciones",
|
||||
"save": "Guardar",
|
||||
"revert": "Revertir",
|
||||
"sectionGeneral": "General",
|
||||
"sectionAdvanced": "Avanzado",
|
||||
"noItems": "Sin elementos",
|
||||
"nullNoItems": "Sin elementos. Usando configuración global",
|
||||
"add": "Añadir"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "Tiempo de sesión",
|
||||
"sessionTimeoutDesc": "Duración de sesión con 'Recordarme' (en segundos)",
|
||||
"metrics": "Métricas",
|
||||
"metricsPassword": "Contraseña",
|
||||
"metricsPasswordDesc": "Contraseña Bearer para el endpoint de métricas (contraseña o hash argon2)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "Ruta para métricas en formato JSON",
|
||||
"prometheus": "Prometheus",
|
||||
"prometheusDesc": "Ruta para métricas de Prometheus"
|
||||
},
|
||||
"config": {
|
||||
"connection": "Conexión",
|
||||
"hostDesc": "Nombre de host público al que se conectarán los clientes (invalida config)",
|
||||
"portDesc": "Puerto UDP público al que se conectarán los clientes (invalida config, también debes cambiar el puerto de interfaz)",
|
||||
"allowedIpsDesc": "IPs permitidas que usarán los clientes (configuración global)",
|
||||
"dnsDesc": "Servidor DNS que usarán los clientes (configuración global)",
|
||||
"mtuDesc": "MTU que usarán los clientes (solo para nuevos clientes)",
|
||||
"persistentKeepaliveDesc": "Intervalo en segundos para enviar keepalives al servidor. 0 = desactivado (solo para nuevos clientes)",
|
||||
"suggest": "Sugerir",
|
||||
"suggestDesc": "Elegir dirección IP o nombre de host para el campo Host"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "CIDR cambiado",
|
||||
"device": "Dispositivo",
|
||||
"deviceDesc": "Dispositivo Ethernet por donde se reenviará el tráfico de WireGuard",
|
||||
"mtuDesc": "MTU que usará WireGuard",
|
||||
"portDesc": "Puerto UDP en el que escuchará WireGuard (debes cambiar también el puerto de config)",
|
||||
"changeCidr": "Cambiar CIDR",
|
||||
"restart": "Reiniciar interfaz",
|
||||
"restartDesc": "Reiniciar la interfaz de WireGuard",
|
||||
"restartWarn": "¿Estás seguro de reiniciar la interfaz? Esto desconectará a todos los clientes.",
|
||||
"restartSuccess": "Interfaz reiniciada"
|
||||
},
|
||||
"introText": "Bienvenido al panel de administración.\n\nAquí puedes gestionar los ajustes generales, la configuración, la interfaz y los hooks.\n\nEmpieza eligiendo una de las secciones en la barra lateral."
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
"required": "{0} es obligatorio",
|
||||
"validNumber": "{0} debe ser un número válido",
|
||||
"validString": "{0} debe ser una cadena válida",
|
||||
"validBoolean": "{0} debe ser un booleano válido",
|
||||
"validArray": "{0} debe ser una lista válida",
|
||||
"stringMin": "{0} debe tener al menos {1} caracteres",
|
||||
"numberMin": "{0} debe ser al menos {1}"
|
||||
},
|
||||
"client": {
|
||||
"id": "ID del cliente",
|
||||
"name": "Nombre",
|
||||
"expiresAt": "Expira el",
|
||||
"address4": "Dirección IPv4",
|
||||
"address6": "Dirección IPv6",
|
||||
"serverAllowedIps": "IPs permitidas del servidor"
|
||||
},
|
||||
"user": {
|
||||
"username": "Usuario",
|
||||
"password": "Contraseña",
|
||||
"remember": "Recordar",
|
||||
"name": "Nombre",
|
||||
"email": "Correo electrónico",
|
||||
"emailInvalid": "El correo electrónico debe ser válido",
|
||||
"passwordMatch": "Las contraseñas deben coincidir",
|
||||
"totpEnable": "Activar TOTP",
|
||||
"totpEnableTrue": "Debe estar activado",
|
||||
"totpCode": "Código TOTP"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "Host"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "Tiempo de sesión",
|
||||
"metricsEnabled": "Métricas",
|
||||
"metricsPassword": "Contraseña de métricas"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "Dispositivo",
|
||||
"cidrValid": "El CIDR debe ser válido"
|
||||
},
|
||||
"otl": "Enlace de un solo uso",
|
||||
"stringMalformed": "Cadena malformada",
|
||||
"body": "El cuerpo debe ser un objeto válido",
|
||||
"hook": "Hook",
|
||||
"enabled": "Habilitado",
|
||||
"mtu": "MTU",
|
||||
"port": "Puerto",
|
||||
"persistentKeepalive": "Keepalive persistente",
|
||||
"address": "Dirección IP",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "IPs permitidas",
|
||||
"file": "Archivo"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,10 @@
|
||||
"hooks": "Hooks",
|
||||
"hooksDescription": "Les hooks ne fonctionnent qu'avec wg-quick",
|
||||
"hooksLeaveEmpty": "Uniquement pour wg-quick. Sinon, laissez-le vide",
|
||||
"dnsDesc": "Serveur DNS que les clients utiliseront (remplace la configuration globale)"
|
||||
"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"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Modifier",
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "Akun",
|
||||
"clients": "Klien",
|
||||
"admin": {
|
||||
"panel": "Panel Admin",
|
||||
"general": "General",
|
||||
"config": "Umum",
|
||||
"interface": "Antarmuka",
|
||||
"hooks": "Hooks"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "E-Mail"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "Kata Sandi Saat Ini",
|
||||
"enable2fa": "Aktifkan autentikasi dua faktor",
|
||||
"enable2faDesc": "Pindai kode QR dengan aplikasi autentikator anda atau masukkan kuncinya secara manual.",
|
||||
"2faKey": "Kunci TOTP",
|
||||
"2faCodeDesc": "Masukkan kode dari aplikasi autentikator Anda.",
|
||||
"disable2fa": "Nonaktifkan autentikasi dua faktor",
|
||||
"disable2faDesc": "Masukkan kata sandi anda untuk menonaktifkan autentikasi dua faktor."
|
||||
},
|
||||
"general": {
|
||||
"name": "Nama",
|
||||
"username": "Name Pengguna",
|
||||
"password": "Kata Sandi",
|
||||
"newPassword": "Kata Sandi Baru",
|
||||
"updatePassword": "Ubah Kata Sandi",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "IP yang diizinkan",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "Keepalive Persisten",
|
||||
"logout": "Keluar",
|
||||
"continue": "Lanjutkan",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"yes": "Ya",
|
||||
"no": "Tidak",
|
||||
"confirmPassword": "Konfirmasi Kata Sandi",
|
||||
"loading": "Memuat...",
|
||||
"2fa": "Autentikasi Dua Faktor",
|
||||
"2faCode": "Kode TOTP"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "Selamat datang di pengaturan pertama wg-easy anda",
|
||||
"welcomeDesc": "Anda telah menemukan cara termudah untuk menginstal dan mengelola WireGuard di host Linux mana pun",
|
||||
"existingSetup": "Apakah anda sudah memiliki pengaturan sebelumnya?",
|
||||
"createAdminDesc": "Silakan masukkan dulu nama pengguna admin dan kata sandi yang kuat dan aman. Informasi ini akan digunakan untuk masuk ke panel administrasi Anda.",
|
||||
"setupConfigDesc": "Silakan masukkan informasi host dan port. Ini akan digunakan untuk konfigurasi klien saat mengatur WireGuard di perangkat mereka.",
|
||||
"setupMigrationDesc": "Silakan berikan file cadangan jika anda ingin memigrasikan data dari versi wg-easy sebelumnya ke pengaturan baru anda.",
|
||||
"upload": "Unggah",
|
||||
"migration": "Pulihkan cadangan:",
|
||||
"createAccount": "Buat Akun",
|
||||
"successful": "Pengaturan berhasil",
|
||||
"hostDesc": "Hostname publik yang akan digunakan klien untuk terhubung",
|
||||
"portDesc": "Port UDP publik yang akan digunakan klien untuk terhubung dan dibaca oleh WireGuard"
|
||||
},
|
||||
"update": {
|
||||
"updateAvailable": "Ada pembaruan tersedia!",
|
||||
"update": "Pembaruan"
|
||||
},
|
||||
"theme": {
|
||||
"dark": "Tema gelap",
|
||||
"light": "Tema terang",
|
||||
"system": "Tema sistem"
|
||||
},
|
||||
"layout": {
|
||||
"toggleCharts": "Tampilkan/sembunyikan Grafik",
|
||||
"donate": "Donasi"
|
||||
},
|
||||
"login": {
|
||||
"signIn": "Masuk",
|
||||
"rememberMe": "Ingat saya",
|
||||
"rememberMeDesc": "Tetap masuk setelah menutup browser",
|
||||
"insecure": "Anda tidak dapat masuk dengan koneksi yang tidak aman. Gunakan HTTPS.",
|
||||
"2faRequired": "Autentikasi dua faktor wajib digunakan",
|
||||
"2faWrong": "Autentikasi dua faktor salah"
|
||||
},
|
||||
"client": {
|
||||
"empty": "Belum ada klien.",
|
||||
"newShort": "Baru",
|
||||
"sort": "Urutkan",
|
||||
"create": "Buat Klien",
|
||||
"created": "Klien terbuat",
|
||||
"new": "Klien Baru",
|
||||
"name": "Nama",
|
||||
"expireDate": "Tanggal Kedaluwarsa",
|
||||
"expireDateDesc": "Tanggal klien akan dinonaktifkan. Kosongkan untuk opsi permanen.",
|
||||
"deleteClient": "Hapus Klien",
|
||||
"deleteDialog1": "Apakah anda yakin ingin menghapus",
|
||||
"deleteDialog2": "Tindakan ini tidak dapat dibatalkan.",
|
||||
"enabled": "Aktifkan",
|
||||
"address": "Alamat",
|
||||
"serverAllowedIps": "IP yang Diizinkan pada Server",
|
||||
"otlDesc": "Buat tautan sekali pakai singkat",
|
||||
"permanent": "Permanen",
|
||||
"createdOn": "Dibuat pada ",
|
||||
"lastSeen": "Terakhir terlihat pada ",
|
||||
"totalDownload": "Total Unduhan: ",
|
||||
"totalUpload": "Total Unggahan: ",
|
||||
"newClient": "Klien Baru",
|
||||
"disableClient": "Nonaktifkan Klien",
|
||||
"enableClient": "Aktifkan Klien",
|
||||
"noPrivKey": "Klien ini tidak memiliki kunci pribadi (private key) yang diketahui. Tidak dapat membuat konfigurasi.",
|
||||
"showQR": "Tampilkan Kode QR",
|
||||
"downloadConfig": "Unduh Konfigurasi",
|
||||
"allowedIpsDesc": "IP mana yang akan dialihkan melalui VPN (menggantikan konfigurasi global)",
|
||||
"serverAllowedIpsDesc": "IP mana yang akan dialihkan server ke klien",
|
||||
"mtuDesc": "Menetapkan unit transmisi maksimum (ukuran paket) untuk terowongan VPN",
|
||||
"persistentKeepaliveDesc": "Menetapkan interval (dalam detik) untuk paket keep-alive. 0 menonaktifkannya.",
|
||||
"hooks": "Hooks",
|
||||
"hooksDescription": "Hooks hanya berfungsi dengan wg-quick",
|
||||
"hooksLeaveEmpty": "Hanya untuk wg-quick. Jika tidak, biarkan kosong.",
|
||||
"dnsDesc": "Server DNS yang akan digunakan klien (menggantikan konfigurasi global)",
|
||||
"notConnected": "Klien tidak terhubung",
|
||||
"endpoint": "Titik Akhir (Endpoint)",
|
||||
"endpointDesc": "IP klien dari mana koneksi WireGuard dibuat"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Ubah",
|
||||
"cancel": "Batal",
|
||||
"create": "Buat"
|
||||
},
|
||||
"toast": {
|
||||
"success": "Berhasil",
|
||||
"saved": "Tersimpan",
|
||||
"error": "Kesalahan"
|
||||
},
|
||||
"form": {
|
||||
"actions": "Tindakan",
|
||||
"save": "Simpan",
|
||||
"revert": "Kembalikan",
|
||||
"sectionGeneral": "Umum",
|
||||
"sectionAdvanced": "Lanjutan",
|
||||
"noItems": "Tidak ada item",
|
||||
"nullNoItems": "Tidak ada item. Menggunakan konfigurasi global",
|
||||
"add": "Tambah"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "Waktu Sesi Habis",
|
||||
"sessionTimeoutDesc": "Durasi sesi untuk Ingat Saya (dalam detik)",
|
||||
"metrics": "Metrik",
|
||||
"metricsPassword": "Kata Sandi",
|
||||
"metricsPasswordDesc": "Kata sandi Bearer untuk endpoint metrik (kata sandi atau hash argon2)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "Rute untuk metrik dalam format JSON",
|
||||
"prometheus": "Prometheus",
|
||||
"prometheusDesc": "Rute untuk metrik Prometheus"
|
||||
},
|
||||
"config": {
|
||||
"connection": "Koneksi",
|
||||
"hostDesc": "Hostname publik yang akan digunakan klien untuk terhubung (membatalkan konfigurasi)",
|
||||
"portDesc": "Port UDP publik yang akan digunakan klien untuk terhubung (membatalkan konfigurasi, anda mungkin ingin mengubah Port Antarmuka juga)",
|
||||
"allowedIpsDesc": "IP yang diizinkan untuk digunakan klien (konfigurasi global)",
|
||||
"dnsDesc": "Server DNS yang akan digunakan klien (konfigurasi global)",
|
||||
"mtuDesc": "MTU yang akan digunakan klien (hanya untuk klien baru)",
|
||||
"persistentKeepaliveDesc": "Interval dalam detik untuk mengirim keepalive ke server. 0 = dinonaktifkan (hanya untuk klien baru)",
|
||||
"suggest": "Menyarankan",
|
||||
"suggestDesc": "Pilih alamat IP atau hostname untuk kolom Host"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "CIDR diperbarui",
|
||||
"device": "Perangkat",
|
||||
"deviceDesc": "Perangkat Ethernet tempat lalu lintas WireGuard harus diteruskan",
|
||||
"mtuDesc": "MTU yang akan digunakan oleh WireGuard",
|
||||
"portDesc": "Port UDP yang akan didengarkan oleh WireGuard (Anda mungkin ingin mengubah Port Konfigurasi juga)",
|
||||
"changeCidr": "Ubah CIDR",
|
||||
"restart": "Mulai Ulang Antarmuka",
|
||||
"restartDesc": "Mulai ulang antarmuka WireGuard",
|
||||
"restartWarn": "Apakah Anda yakin ingin memulai ulang antarmuka? Ini akan membuat semua klien menjadi terputus.",
|
||||
"restartSuccess": "Antarmuka telah dimulai ulang."
|
||||
},
|
||||
"introText": "Selamat datang di panel admin.\n\nDi sini Anda dapat mengelola pengaturan umum, konfigurasi, pengaturan antarmuka, dan hooks.\n\nMulailah dengan memilih salah satu bagian di bilah samping."
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
"required": "{0} diperlukan",
|
||||
"validNumber": "{0} harus berupa angka yang valid",
|
||||
"validString": "{0} harus berupa string yang valid",
|
||||
"validBoolean": "{0} harus berupa boolean yang valid",
|
||||
"validArray": "{0} harus berupa array yang valid",
|
||||
"stringMin": "{0} harus memiliki setidaknya {1} karakter",
|
||||
"numberMin": "{0} harus minimal {1}"
|
||||
},
|
||||
"client": {
|
||||
"id": "ID Klien",
|
||||
"name": "Nama",
|
||||
"expiresAt": "Berakhir pada",
|
||||
"address4": "Alamat IPv4",
|
||||
"address6": "Alamat IPv6",
|
||||
"serverAllowedIps": "IP yang diizinkan untuk Server"
|
||||
},
|
||||
"user": {
|
||||
"username": "Nama Pengguna",
|
||||
"password": "Kata Sandi",
|
||||
"remember": "Ingat",
|
||||
"name": "Nama",
|
||||
"email": "Email",
|
||||
"emailInvalid": "Harus berupa email yang valid",
|
||||
"passwordMatch": "Kata sandi harus cocok",
|
||||
"totpEnable": "Aktifkan TOTP",
|
||||
"totpEnableTrue": "Aktifkan TOTP harus bernilai true",
|
||||
"totpCode": "Kode TOTP"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "Host"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "Waktu Sesi Habis",
|
||||
"metricsEnabled": "Metrik",
|
||||
"metricsPassword": "Kata Sandi Metrik"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "Perangkat",
|
||||
"cidrValid": "CIDR harus valid"
|
||||
},
|
||||
"otl": "Tautan Sekali Pakai",
|
||||
"stringMalformed": "String tidak terformat dengan benar",
|
||||
"body": "Body harus berupa objek yang valid",
|
||||
"hook": "Hook",
|
||||
"enabled": "Diaktifkan",
|
||||
"mtu": "MTU",
|
||||
"port": "Port",
|
||||
"persistentKeepalive": "Keepalive Persisten",
|
||||
"address": "Alamat IP",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "IP yang diizinkan",
|
||||
"file": "Berkas"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "Account",
|
||||
"clients": "Client",
|
||||
"admin": {
|
||||
"panel": "Pannello di Amministrazione",
|
||||
"general": "Generale",
|
||||
"config": "Configurazione",
|
||||
"interface": "Interfaccia",
|
||||
"hooks": "Hooks"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "E-Mail"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "Password attuale",
|
||||
"enable2fa": "Abilita Autenticazione a Due Fattori",
|
||||
"enable2faDesc": "Scansiona il codice QR con la tua app di autenticazione o inserisci manualmente la chiave.",
|
||||
"2faKey": "Chiave TOTP",
|
||||
"2faCodeDesc": "Inserisci il codice generato dalla tua app di autenticazione.",
|
||||
"disable2fa": "Disabilita Autenticazione a Due Fattori",
|
||||
"disable2faDesc": "Inserisci la tua password per disabilitare l’Autenticazione a Due Fattori."
|
||||
},
|
||||
"general": {
|
||||
"name": "Nome",
|
||||
"username": "Nome utente",
|
||||
"password": "Password",
|
||||
"newPassword": "Nuova Password",
|
||||
"updatePassword": "Aggiorna Password",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "IP consentiti",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "Keepalive Persistente",
|
||||
"logout": "Esci",
|
||||
"continue": "Continua",
|
||||
"host": "Host",
|
||||
"port": "Porta",
|
||||
"yes": "Sì",
|
||||
"no": "No",
|
||||
"confirmPassword": "Conferma Password",
|
||||
"loading": "Caricamento...",
|
||||
"2fa": "Autenticazione a Due Fattori",
|
||||
"2faCode": "Codice TOTP"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "Benvenuto alla tua prima configurazione di wg-easy",
|
||||
"welcomeDesc": "Hai trovato il modo più semplice per installare e gestire WireGuard su qualsiasi host Linux",
|
||||
"existingSetup": "Hai già una configurazione esistente?",
|
||||
"createAdminDesc": "Inserisci prima un nome utente admin e una password forte e sicura. Queste informazioni verranno usate per accedere al pannello di amministrazione.",
|
||||
"setupConfigDesc": "Inserisci le informazioni su host e porta. Saranno usate per la configurazione dei client quando imposteranno WireGuard sui loro dispositivi.",
|
||||
"setupMigrationDesc": "Fornisci il file di backup se vuoi migrare i tuoi dati dalla precedente versione di wg-easy alla nuova installazione.",
|
||||
"upload": "Carica",
|
||||
"migration": "Ripristina backup:",
|
||||
"createAccount": "Crea Account",
|
||||
"successful": "Configurazione completata con successo",
|
||||
"hostDesc": "Nome host pubblico a cui i client si connetteranno",
|
||||
"portDesc": "Porta UDP pubblica a cui i client si connetteranno e su cui WireGuard resterà in ascolto"
|
||||
},
|
||||
"update": {
|
||||
"updateAvailable": "È disponibile un aggiornamento!",
|
||||
"update": "Aggiorna"
|
||||
},
|
||||
"theme": {
|
||||
"dark": "Tema scuro",
|
||||
"light": "Tema chiaro",
|
||||
"system": "Tema di sistema"
|
||||
},
|
||||
"layout": {
|
||||
"toggleCharts": "Mostra/nascondi Grafici",
|
||||
"donate": "Dona"
|
||||
},
|
||||
"login": {
|
||||
"signIn": "Accedi",
|
||||
"rememberMe": "Ricordami",
|
||||
"rememberMeDesc": "Rimani connesso dopo aver chiuso il browser",
|
||||
"insecure": "Non puoi accedere con una connessione non sicura. Usa HTTPS.",
|
||||
"2faRequired": "È richiesta l’Autenticazione a Due Fattori",
|
||||
"2faWrong": "Codice di Autenticazione a Due Fattori errato"
|
||||
},
|
||||
"client": {
|
||||
"empty": "Non ci sono ancora client.",
|
||||
"newShort": "Nuovo",
|
||||
"sort": "Ordina",
|
||||
"create": "Crea Client",
|
||||
"created": "Client creato",
|
||||
"new": "Nuovo Client",
|
||||
"name": "Nome",
|
||||
"expireDate": "Data di Scadenza",
|
||||
"expireDateDesc": "Data in cui il client verrà disabilitato. Lascia vuoto per permanente",
|
||||
"deleteClient": "Elimina Client",
|
||||
"deleteDialog1": "Sei sicuro di voler eliminare",
|
||||
"deleteDialog2": "Questa azione non può essere annullata.",
|
||||
"enabled": "Abilitato",
|
||||
"address": "Indirizzo",
|
||||
"serverAllowedIps": "IP consentiti dal Server",
|
||||
"otlDesc": "Genera link temporaneo monouso",
|
||||
"permanent": "Permanente",
|
||||
"createdOn": "Creato il ",
|
||||
"lastSeen": "Ultima connessione il ",
|
||||
"totalDownload": "Download Totale: ",
|
||||
"totalUpload": "Upload Totale: ",
|
||||
"newClient": "Nuovo Client",
|
||||
"disableClient": "Disabilita Client",
|
||||
"enableClient": "Abilita Client",
|
||||
"noPrivKey": "Questo client non ha una chiave privata nota. Impossibile creare la configurazione.",
|
||||
"showQR": "Mostra Codice QR",
|
||||
"downloadConfig": "Scarica Configurazione",
|
||||
"allowedIpsDesc": "Quali IP verranno instradati attraverso la VPN (sovrascrive la config globale)",
|
||||
"serverAllowedIpsDesc": "Quali IP il server instraderà al client",
|
||||
"mtuDesc": "Imposta la dimensione massima dei pacchetti (MTU) per il tunnel VPN",
|
||||
"persistentKeepaliveDesc": "Imposta l’intervallo (in secondi) dei pacchetti keep-alive. 0 lo disabilita",
|
||||
"hooks": "Hooks",
|
||||
"hooksDescription": "Gli hooks funzionano solo con wg-quick",
|
||||
"hooksLeaveEmpty": "Solo per wg-quick. Altrimenti lascia vuoto",
|
||||
"dnsDesc": "Server DNS che i client useranno (sovrascrive la config globale)",
|
||||
"notConnected": "Client non connesso",
|
||||
"endpoint": "Endpoint",
|
||||
"endpointDesc": "IP del client da cui viene stabilita la connessione WireGuard"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Modifica",
|
||||
"cancel": "Annulla",
|
||||
"create": "Crea"
|
||||
},
|
||||
"toast": {
|
||||
"success": "Operazione riuscita",
|
||||
"saved": "Salvato",
|
||||
"error": "Errore"
|
||||
},
|
||||
"form": {
|
||||
"actions": "Azioni",
|
||||
"save": "Salva",
|
||||
"revert": "Annulla modifiche",
|
||||
"sectionGeneral": "Generale",
|
||||
"sectionAdvanced": "Avanzate",
|
||||
"noItems": "Nessun elemento",
|
||||
"nullNoItems": "Nessun elemento. Uso configurazione globale",
|
||||
"add": "Aggiungi"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "Timeout Sessione",
|
||||
"sessionTimeoutDesc": "Durata della sessione per 'Ricordami' (in secondi)",
|
||||
"metrics": "Metriche",
|
||||
"metricsPassword": "Password",
|
||||
"metricsPasswordDesc": "Password Bearer per l’endpoint metriche (password o hash argon2)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "Percorso per metriche in formato JSON",
|
||||
"prometheus": "Prometheus",
|
||||
"prometheusDesc": "Percorso per metriche Prometheus"
|
||||
},
|
||||
"config": {
|
||||
"connection": "Connessione",
|
||||
"hostDesc": "Nome host pubblico a cui i client si connetteranno (invalida la config)",
|
||||
"portDesc": "Porta UDP pubblica a cui i client si connetteranno (invalida la config, probabilmente vuoi cambiare anche la Porta Interfaccia)",
|
||||
"allowedIpsDesc": "IP consentiti che i client useranno (config globale)",
|
||||
"dnsDesc": "Server DNS che i client useranno (config globale)",
|
||||
"mtuDesc": "MTU usata dai client (solo per i nuovi client)",
|
||||
"persistentKeepaliveDesc": "Intervallo in secondi per inviare keepalive al server. 0 = disabilitato (solo per nuovi client)",
|
||||
"suggest": "Suggerisci",
|
||||
"suggestDesc": "Scegli un indirizzo IP o un Hostname per il campo Host"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "CIDR modificato",
|
||||
"device": "Dispositivo",
|
||||
"deviceDesc": "Dispositivo di rete attraverso cui instradare il traffico WireGuard",
|
||||
"mtuDesc": "MTU usata da WireGuard",
|
||||
"portDesc": "Porta UDP su cui WireGuard resterà in ascolto (probabilmente vuoi cambiare anche la Porta Config)",
|
||||
"changeCidr": "Modifica CIDR",
|
||||
"restart": "Riavvia Interfaccia",
|
||||
"restartDesc": "Riavvia l’interfaccia WireGuard",
|
||||
"restartWarn": "Sei sicuro di riavviare l’interfaccia? Tutti i client verranno disconnessi.",
|
||||
"restartSuccess": "Interfaccia riavviata"
|
||||
},
|
||||
"introText": "Benvenuto nel pannello di amministrazione.\n\nQui puoi gestire le impostazioni generali, la configurazione, le impostazioni dell’interfaccia e gli hooks.\n\nInizia scegliendo una delle sezioni nella barra laterale."
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
"required": "{0} è obbligatorio",
|
||||
"validNumber": "{0} deve essere un numero valido",
|
||||
"validString": "{0} deve essere una stringa valida",
|
||||
"validBoolean": "{0} deve essere un valore booleano valido",
|
||||
"validArray": "{0} deve essere un array valido",
|
||||
"stringMin": "{0} deve contenere almeno {1} carattere",
|
||||
"numberMin": "{0} deve essere almeno {1}"
|
||||
},
|
||||
"client": {
|
||||
"id": "ID Client",
|
||||
"name": "Nome",
|
||||
"expiresAt": "Scadenza",
|
||||
"address4": "Indirizzo IPv4",
|
||||
"address6": "Indirizzo IPv6",
|
||||
"serverAllowedIps": "IP consentiti dal Server"
|
||||
},
|
||||
"user": {
|
||||
"username": "Nome utente",
|
||||
"password": "Password",
|
||||
"remember": "Ricorda",
|
||||
"name": "Nome",
|
||||
"email": "Email",
|
||||
"emailInvalid": "Email deve essere un indirizzo valido",
|
||||
"passwordMatch": "Le password devono coincidere",
|
||||
"totpEnable": "Abilitazione TOTP",
|
||||
"totpEnableTrue": "TOTP deve essere abilitato",
|
||||
"totpCode": "Codice TOTP"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "Host"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "Timeout Sessione",
|
||||
"metricsEnabled": "Metriche",
|
||||
"metricsPassword": "Password Metriche"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "Dispositivo",
|
||||
"cidrValid": "CIDR deve essere valido"
|
||||
},
|
||||
"otl": "Link Monouso",
|
||||
"stringMalformed": "Stringa non valida",
|
||||
"body": "Body deve essere un oggetto valido",
|
||||
"hook": "Hook",
|
||||
"enabled": "Abilitato",
|
||||
"mtu": "MTU",
|
||||
"port": "Porta",
|
||||
"persistentKeepalive": "Keepalive Persistente",
|
||||
"address": "Indirizzo IP",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "IP consentiti",
|
||||
"file": "File"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "계정",
|
||||
"clients": "클라이언트",
|
||||
"admin": {
|
||||
"panel": "관리 패널",
|
||||
"general": "일반",
|
||||
"config": "구성",
|
||||
"interface": "인터페이스",
|
||||
"hooks": "후크"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "이메일"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "현재 비밀번호",
|
||||
"enable2fa": "2단계 인증 활성화",
|
||||
"enable2faDesc": "인증 앱으로 QR 코드를 스캔하거나 키를 수동으로 입력하세요.",
|
||||
"2faKey": "TOTP 키",
|
||||
"2faCodeDesc": "인증기 앱에서 코드를 입력하십시오.",
|
||||
"disable2fa": "2단계 인증 비활성화",
|
||||
"disable2faDesc": "2단계 인증을 비활성화하려면 비밀번호를 입력하세요."
|
||||
},
|
||||
"general": {
|
||||
"name": "이름",
|
||||
"username": "사용자 이름",
|
||||
"password": "비밀번호",
|
||||
"newPassword": "새 비밀번호",
|
||||
"updatePassword": "비밀번호 업데이트",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "허용된 IP",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "지속적인 유지",
|
||||
"logout": "로그아웃",
|
||||
"continue": "계속",
|
||||
"host": "호스트",
|
||||
"port": "포트",
|
||||
"yes": "예",
|
||||
"no": "아니요",
|
||||
"confirmPassword": "비밀번호 확인",
|
||||
"loading": "로딩 중...",
|
||||
"2fa": "2단계 인증",
|
||||
"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": "2단계 인증이 필요합니다",
|
||||
"2faWrong": "2단계 인증이 잘못되었습니다"
|
||||
},
|
||||
"client": {
|
||||
"empty": "아직 클라이언트가 없습니다.",
|
||||
"newShort": "새로 만들기",
|
||||
"sort": "정렬",
|
||||
"create": "클라이언트 생성",
|
||||
"created": "클라이언트 생성됨",
|
||||
"new": "새 클라이언트",
|
||||
"name": "이름",
|
||||
"expireDate": "만료 날짜",
|
||||
"expireDateDesc": "클라이언트가 비활성화될 날짜. 영구적으로 비워두기",
|
||||
"deleteClient": "클라이언트 삭제",
|
||||
"deleteDialog1": "정말로 삭제하시겠습니까",
|
||||
"deleteDialog2": "이 작업은 실행 취소할 수 없습니다.",
|
||||
"enabled": "활성화됨",
|
||||
"address": "주소",
|
||||
"serverAllowedIps": "서버 허용된 IP",
|
||||
"otlDesc": "짧은 일회성 링크 생성",
|
||||
"permanent": "영구적",
|
||||
"createdOn": "생성일 ",
|
||||
"lastSeen": "마지막으로 본 시간 ",
|
||||
"totalDownload": "총 다운로드: ",
|
||||
"totalUpload": "총 업로드: ",
|
||||
"newClient": "새 클라이언트",
|
||||
"disableClient": "클라이언트 비활성화",
|
||||
"enableClient": "클라이언트 활성화",
|
||||
"noPrivKey": "이 클라이언트는 알려진 개인 키가 없습니다. 구성을 생성할 수 없습니다.",
|
||||
"showQR": "QR 코드 표시",
|
||||
"downloadConfig": "구성 다운로드",
|
||||
"allowedIpsDesc": "VPN을 통해 라우팅될 IP(전역 구성 재정의)",
|
||||
"serverAllowedIpsDesc": "서버가 클라이언트로 라우팅할 IPs",
|
||||
"mtuDesc": "VPN 터널의 최대 전송 단위(패킷 크기)를 설정합니다",
|
||||
"persistentKeepaliveDesc": "유지 관리 패킷의 간격(초 단위)을 설정합니다. 0은 비활성화합니다.",
|
||||
"hooks": "후크",
|
||||
"hooksDescription": "후크는 wg-quick과만 작동합니다",
|
||||
"hooksLeaveEmpty": "wg-quick 전용입니다. 그렇지 않으면 비워 두십시오.",
|
||||
"dnsDesc": "클라이언트가 사용할 DNS 서버(전역 구성 무시)",
|
||||
"notConnected": "클라이언트가 연결되지 않음",
|
||||
"endpoint": "엔드포인트",
|
||||
"endpointDesc": "WireGuard에 연결된 클라이언트의 IP 주소"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "변경",
|
||||
"cancel": "취소",
|
||||
"create": "생성"
|
||||
},
|
||||
"toast": {
|
||||
"success": "성공",
|
||||
"saved": "저장됨",
|
||||
"error": "오류"
|
||||
},
|
||||
"form": {
|
||||
"actions": "작업",
|
||||
"save": "저장",
|
||||
"revert": "되돌리기",
|
||||
"sectionGeneral": "일반",
|
||||
"sectionAdvanced": "고급",
|
||||
"noItems": "항목 없음",
|
||||
"nullNoItems": "항목이 없습니다. 전역 구성 사용 중",
|
||||
"add": "추가"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "세션 타임아웃",
|
||||
"sessionTimeoutDesc": "기억하기 위한 세션 지속 시간(초)",
|
||||
"metrics": "메트릭",
|
||||
"metricsPassword": "비밀번호",
|
||||
"metricsPasswordDesc": "메트릭 엔드포인트에 대한 베어러 비밀번호(비밀번호 또는 argon2 해시)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "JSON 형식의 메트릭 경로",
|
||||
"prometheus": "프로메테우스",
|
||||
"prometheusDesc": "Prometheus 메트릭을 위한 경로"
|
||||
},
|
||||
"config": {
|
||||
"connection": "연결",
|
||||
"hostDesc": "클라이언트가 연결할 공용 호스트 이름(구성 무효화)",
|
||||
"portDesc": "공용 UDP 포트 클라이언트가 연결할 (구성을 무효화하며, 인터페이스 포트도 변경하는 것이 좋습니다)",
|
||||
"allowedIpsDesc": "클라이언트가 사용할 수 있는 허용된 IP(전역 설정)",
|
||||
"dnsDesc": "클라이언트가 사용할 DNS 서버(전역 설정)",
|
||||
"mtuDesc": "MTU 클라이언트가 사용할 (신규 클라이언트 전용)",
|
||||
"persistentKeepaliveDesc": "서버에 유지 신호를 보내기 위한 간격(초). 0 = 비활성화(새 클라이언트 전용)",
|
||||
"suggest": "제안",
|
||||
"suggestDesc": "호스트 필드에 사용할 IP 주소 또는 호스트 이름을 선택하세요."
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "CIDR가 변경되었습니다.",
|
||||
"device": "장치",
|
||||
"deviceDesc": "WireGuard 트래픽이 전달되어야 하는 이더넷 장치",
|
||||
"mtuDesc": "MTU WireGuard가 사용할 값",
|
||||
"portDesc": "WireGuard가 수신할 UDP 포트 (구성 포트도 변경하는 것이 좋습니다)",
|
||||
"changeCidr": "CIDR 변경",
|
||||
"restart": "인터페이스 재시작",
|
||||
"restartDesc": "WireGuard 인터페이스를 재시작합니다.",
|
||||
"restartWarn": "인터페이스를 재시작하시겠습니까? 이 작업은 모든 클라이언트를 연결 해제합니다.",
|
||||
"restartSuccess": "인터페이스가 재시작되었습니다"
|
||||
},
|
||||
"introText": "관리 패널에 오신 것을 환영합니다.\n\n여기에서 일반 설정, 구성, 인터페이스 설정 및 후크를 관리할 수 있습니다.\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 사용은 true여야 합니다.",
|
||||
"totpCode": "TOTP 코드"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "호스트"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "세션 타임아웃",
|
||||
"metricsEnabled": "메트릭",
|
||||
"metricsPassword": "메트릭 비밀번호"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "장치",
|
||||
"cidrValid": "CIDR는 유효해야 합니다."
|
||||
},
|
||||
"otl": "일회성 링크",
|
||||
"stringMalformed": "문자열이 잘못되었습니다.",
|
||||
"body": "본문은 유효한 객체여야 합니다",
|
||||
"hook": "후크",
|
||||
"enabled": "활성화됨",
|
||||
"mtu": "MTU",
|
||||
"port": "포트",
|
||||
"persistentKeepalive": "지속적인 유지",
|
||||
"address": "IP 주소",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "허용된 IP",
|
||||
"file": "파일"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "Konto",
|
||||
"clients": "Konta",
|
||||
"admin": {
|
||||
"panel": "Panel administracyjny",
|
||||
"general": "Ogólne",
|
||||
"config": "Konfiguracja",
|
||||
"interface": "Interfejs",
|
||||
"hooks": "Hooki"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "E-mail"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "Aktualne hasło",
|
||||
"enable2fa": "Włącz uwierzytelnianie dwuskładnikowe",
|
||||
"enable2faDesc": "Zeskanuj kod QR w aplikacji uwierzytelniającej lub wprowadź klucz ręcznie.",
|
||||
"2faKey": "Klucz TOTP",
|
||||
"2faCodeDesc": "Wprowadź kod z aplikacji uwierzytelniającej.",
|
||||
"disable2fa": "Wyłącz uwierzytelnianie dwuskładnikowe",
|
||||
"disable2faDesc": "Wprowadź swoje hasło, aby wyłączyć uwierzytelnianie dwuskładnikowe."
|
||||
},
|
||||
"general": {
|
||||
"name": "Nazwa",
|
||||
"username": "Nazwa użytkownika",
|
||||
"password": "Hasło",
|
||||
"newPassword": "Nowe hasło",
|
||||
"updatePassword": "Zmień hasło",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "Dozwolone adresy IP",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "Stałe utrzymywanie połączenia",
|
||||
"logout": "Wyloguj",
|
||||
"continue": "Kontynuuj",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"yes": "Tak",
|
||||
"no": "Nie",
|
||||
"confirmPassword": "Potwierdź hasło",
|
||||
"loading": "Ładowanie...",
|
||||
"2fa": "Uwierzytelnianie dwuskładnikowe",
|
||||
"2faCode": "Kod TOTP"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "Witamy w pierwszej konfiguracji wg-easy",
|
||||
"welcomeDesc": "Znalazłeś najprostszy sposób na instalację i zarządzanie WireGuard na dowolnym serwerze Linux",
|
||||
"existingSetup": "Masz już istniejącą konfigurację?",
|
||||
"createAdminDesc": "Podaj nazwę użytkownika administratora oraz silne, bezpieczne hasło. Informacje te będą używane do logowania w panelu administracyjnym.",
|
||||
"setupConfigDesc": "Podaj informacje o hoście i porcie. Będą one używane w konfiguracji klienta podczas uruchamiania WireGuard na jego urządzeniach.",
|
||||
"setupMigrationDesc": "Podaj plik kopii zapasowej, jeśli chcesz przenieść dane ze starszej wersji wg-easy do nowej instalacji.",
|
||||
"upload": "Prześlij",
|
||||
"migration": "Przywróć kopię zapasową:",
|
||||
"createAccount": "Utwórz konto",
|
||||
"successful": "Konfiguracja zakończona sukcesem",
|
||||
"hostDesc": "Publiczny host, do którego będą łączyć się klienci",
|
||||
"portDesc": "Publiczny port UDP, na którym WireGuard będzie nasłuchiwał i do którego będą łączyć się klienci"
|
||||
},
|
||||
"update": {
|
||||
"updateAvailable": "Dostępna jest aktualizacja!",
|
||||
"update": "Aktualizuj"
|
||||
},
|
||||
"theme": {
|
||||
"dark": "Motyw ciemny",
|
||||
"light": "Motyw jasny",
|
||||
"system": "Motyw systemowy"
|
||||
},
|
||||
"layout": {
|
||||
"toggleCharts": "Pokaż/ukryj wykresy",
|
||||
"donate": "Wesprzyj"
|
||||
},
|
||||
"login": {
|
||||
"signIn": "Zaloguj się",
|
||||
"rememberMe": "Zapamiętaj mnie",
|
||||
"rememberMeDesc": "Pozostań zalogowany po zamknięciu przeglądarki",
|
||||
"insecure": "Nie możesz się zalogować przez niezabezpieczone połączenie. Użyj HTTPS.",
|
||||
"2faRequired": "Wymagane uwierzytelnianie dwuskładnikowe",
|
||||
"2faWrong": "Nieprawidłowy kod uwierzytelniania dwuskładnikowego"
|
||||
},
|
||||
"client": {
|
||||
"empty": "Brak klientów.",
|
||||
"newShort": "Nowy",
|
||||
"sort": "Sortuj",
|
||||
"create": "Utwórz klienta",
|
||||
"created": "Klient utworzony",
|
||||
"new": "Nowy klient",
|
||||
"name": "Nazwa",
|
||||
"expireDate": "Data wygaśnięcia",
|
||||
"expireDateDesc": "Data, po której klient zostanie wyłączony. Puste = na stałe",
|
||||
"deleteClient": "Usuń klienta",
|
||||
"deleteDialog1": "Czy na pewno chcesz usunąć",
|
||||
"deleteDialog2": "Tej akcji nie można cofnąć.",
|
||||
"enabled": "Włączony",
|
||||
"address": "Adres",
|
||||
"serverAllowedIps": "Dozwolone adresy IP serwera",
|
||||
"otlDesc": "Wygeneruj jednorazowy krótki link",
|
||||
"permanent": "Stały",
|
||||
"createdOn": "Utworzony ",
|
||||
"lastSeen": "Ostatnio widziany ",
|
||||
"totalDownload": "Łączne pobieranie: ",
|
||||
"totalUpload": "Łączne wysyłanie: ",
|
||||
"newClient": "Nowy klient",
|
||||
"disableClient": "Wyłącz klienta",
|
||||
"enableClient": "Włącz klienta",
|
||||
"noPrivKey": "Ten klient nie ma znanego klucza prywatnego. Nie można utworzyć konfiguracji.",
|
||||
"showQR": "Pokaż kod QR",
|
||||
"downloadConfig": "Pobierz konfigurację",
|
||||
"allowedIpsDesc": "Które adresy IP będą kierowane przez VPN (nadpisuje konfigurację globalną)",
|
||||
"serverAllowedIpsDesc": "Które adresy IP serwer będzie kierował do klienta",
|
||||
"mtuDesc": "Ustawia maksymalny rozmiar pakietu (MTU) dla tunelu VPN",
|
||||
"persistentKeepaliveDesc": "Ustawia interwał (w sekundach) wysyłania pakietów keep-alive. 0 = wyłączone",
|
||||
"hooks": "Hooki",
|
||||
"hooksDescription": "Hooki działają tylko z wg-quick",
|
||||
"hooksLeaveEmpty": "Tylko dla wg-quick. W innym przypadku pozostaw puste",
|
||||
"dnsDesc": "Serwer DNS, którego będą używać klienci (nadpisuje konfigurację globalną)",
|
||||
"notConnected": "Klient nie jest połączony",
|
||||
"endpoint": "Punkt końcowy",
|
||||
"endpointDesc": "Adres IP klienta, z którego ustanawiane jest połączenie WireGuard"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Zmień",
|
||||
"cancel": "Anuluj",
|
||||
"create": "Utwórz"
|
||||
},
|
||||
"toast": {
|
||||
"success": "Sukces",
|
||||
"saved": "Zapisano",
|
||||
"error": "Błąd"
|
||||
},
|
||||
"form": {
|
||||
"actions": "Akcje",
|
||||
"save": "Zapisz",
|
||||
"revert": "Przywróć",
|
||||
"sectionGeneral": "Ogólne",
|
||||
"sectionAdvanced": "Zaawansowane",
|
||||
"noItems": "Brak elementów",
|
||||
"nullNoItems": "Brak elementów. Używana konfiguracja globalna",
|
||||
"add": "Dodaj"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "Limit czasu sesji",
|
||||
"sessionTimeoutDesc": "Czas trwania sesji dla opcji Zapamiętaj mnie (w sekundach)",
|
||||
"metrics": "Metryki",
|
||||
"metricsPassword": "Hasło",
|
||||
"metricsPasswordDesc": "Hasło Bearer dla endpointu metryk (hasło lub hash argon2)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "Ścieżka dla metryk w formacie JSON",
|
||||
"prometheus": "Prometheus",
|
||||
"prometheusDesc": "Ścieżka dla metryk Prometheus"
|
||||
},
|
||||
"config": {
|
||||
"connection": "Połączenie",
|
||||
"hostDesc": "Publiczny host, do którego będą łączyć się klienci (unieważnia konfigurację)",
|
||||
"portDesc": "Publiczny port UDP, do którego będą łączyć się klienci (unieważnia konfigurację, prawdopodobnie należy też zmienić port interfejsu)",
|
||||
"allowedIpsDesc": "Dozwolone adresy IP klientów (konfiguracja globalna)",
|
||||
"dnsDesc": "Serwer DNS klientów (konfiguracja globalna)",
|
||||
"mtuDesc": "MTU klientów (tylko dla nowych klientów)",
|
||||
"persistentKeepaliveDesc": "Interwał w sekundach dla keepalive do serwera. 0 = wyłączone (tylko dla nowych klientów)",
|
||||
"suggest": "Sugeruj",
|
||||
"suggestDesc": "Wybierz adres IP lub nazwę hosta dla pola Host"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "Zmieniono CIDR",
|
||||
"device": "Urządzenie",
|
||||
"deviceDesc": "Urządzenie sieciowe, przez które ma być przekazywany ruch WireGuard",
|
||||
"mtuDesc": "MTU używane przez WireGuard",
|
||||
"portDesc": "Port UDP, na którym WireGuard będzie nasłuchiwał (prawdopodobnie trzeba też zmienić port w konfiguracji)",
|
||||
"changeCidr": "Zmień CIDR",
|
||||
"restart": "Restart interfejsu",
|
||||
"restartDesc": "Zrestartuj interfejs WireGuard",
|
||||
"restartWarn": "Czy na pewno chcesz zrestartować interfejs? Wszyscy klienci zostaną rozłączeni.",
|
||||
"restartSuccess": "Interfejs zrestartowany"
|
||||
},
|
||||
"introText": "Witamy w panelu administracyjnym.\n\nTutaj możesz zarządzać ustawieniami ogólnymi, konfiguracją, interfejsem i hookami.\n\nZacznij od wybrania jednej z sekcji w pasku bocznym."
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
"required": "{0} jest wymagane",
|
||||
"validNumber": "{0} musi być prawidłową liczbą",
|
||||
"validString": "{0} musi być prawidłowym tekstem",
|
||||
"validBoolean": "{0} musi być prawidłową wartością logiczną",
|
||||
"validArray": "{0} musi być prawidłową tablicą",
|
||||
"stringMin": "{0} musi mieć co najmniej {1} znaków",
|
||||
"numberMin": "{0} musi być co najmniej {1}"
|
||||
},
|
||||
"client": {
|
||||
"id": "ID klienta",
|
||||
"name": "Nazwa",
|
||||
"expiresAt": "Wygasa",
|
||||
"address4": "Adres IPv4",
|
||||
"address6": "Adres IPv6",
|
||||
"serverAllowedIps": "Dozwolone adresy IP serwera"
|
||||
},
|
||||
"user": {
|
||||
"username": "Nazwa użytkownika",
|
||||
"password": "Hasło",
|
||||
"remember": "Zapamiętaj",
|
||||
"name": "Imię",
|
||||
"email": "E-mail",
|
||||
"emailInvalid": "E-mail musi być prawidłowy",
|
||||
"passwordMatch": "Hasła muszą się zgadzać",
|
||||
"totpEnable": "Włącz TOTP",
|
||||
"totpEnableTrue": "TOTP musi być włączone",
|
||||
"totpCode": "Kod TOTP"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "Host"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "Limit czasu sesji",
|
||||
"metricsEnabled": "Metryki",
|
||||
"metricsPassword": "Hasło do metryk"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "Urządzenie",
|
||||
"cidrValid": "CIDR musi być prawidłowy"
|
||||
},
|
||||
"otl": "Jednorazowy link",
|
||||
"stringMalformed": "Nieprawidłowy format tekstu",
|
||||
"body": "Treść musi być prawidłowym obiektem",
|
||||
"hook": "Hook",
|
||||
"enabled": "Włączone",
|
||||
"mtu": "MTU",
|
||||
"port": "Port",
|
||||
"persistentKeepalive": "Stałe utrzymywanie połączenia",
|
||||
"address": "Adres IP",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "Dozwolone adresy IP",
|
||||
"file": "Plik"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "Conta",
|
||||
"clients": "Clientes",
|
||||
"admin": {
|
||||
"panel": "Panel de administração.",
|
||||
"general": "Geral",
|
||||
"config": "Configuração",
|
||||
"interface": "Interface",
|
||||
"hooks": "Hooks"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "E-mail"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "Senha atual",
|
||||
"enable2fa": "Habilitar autenticação em dois fatores",
|
||||
"enable2faDesc": "Escaneie o QR Code com seu aplicativo autenticador ou insira a chave manualmente.",
|
||||
"2faKey": "Chave TOTP",
|
||||
"2faCodeDesc": "Digite o código do seu aplicativo autenticador.",
|
||||
"disable2fa": "Desativar autenticação de dois fatores.",
|
||||
"disable2faDesc": "Digite sua senha para desativar a autenticação de dois fatores."
|
||||
},
|
||||
"general": {
|
||||
"name": "Nome",
|
||||
"username": "Nome de usuário",
|
||||
"password": "Senha",
|
||||
"newPassword": "Nova senha",
|
||||
"updatePassword": "Atualizar senha",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "IPs permitidos",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "Keepalive persistente",
|
||||
"logout": "Sair",
|
||||
"continue": "Continue",
|
||||
"host": "Host",
|
||||
"port": "Porta",
|
||||
"yes": "Sim",
|
||||
"no": "Não",
|
||||
"confirmPassword": "Confirmar senha",
|
||||
"loading": "Carregando...",
|
||||
"2fa": "Autenticação de dois fatores",
|
||||
"2faCode": "Código TOTP"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "Bem-vindo à sua primeira configuração do wg-easy",
|
||||
"welcomeDesc": "Você encontrou a maneira mais fácil de instalar e gerenciar o WireGuard em qualquer host Linux",
|
||||
"existingSetup": "Você já tem uma configuração?",
|
||||
"createAdminDesc": "Primeiro, insira um nome de usuário de administrador e uma senha forte e segura. Essas informações serão usadas para fazer login no seu painel de administração.",
|
||||
"setupConfigDesc": "Insira as informações do host e da porta. Essas informações serão usadas para a configuração do cliente ao instalar o WireGuard em seus dispositivos.",
|
||||
"setupMigrationDesc": "Forneça o arquivo de backup se quiser migrar seus dados da versão anterior do wg-easy para a nova configuração.",
|
||||
"upload": "Upload",
|
||||
"migration": "Restaurar backup:",
|
||||
"createAccount": "Criar conta",
|
||||
"successful": "Configuração bem-sucedida",
|
||||
"hostDesc": "Nome de host público que os clientes se conectarão",
|
||||
"portDesc": "Porta UDP pública que os clientes se conectarão e o WireGuard escutará"
|
||||
},
|
||||
"update": {
|
||||
"updateAvailable": "Há uma atualização disponível!",
|
||||
"update": "Atualizar"
|
||||
},
|
||||
"theme": {
|
||||
"dark": "Tema escuro",
|
||||
"light": "Tema claro",
|
||||
"system": "Tema do sistema"
|
||||
},
|
||||
"layout": {
|
||||
"toggleCharts": "Mostrar/ocultar gráficos",
|
||||
"donate": "Doar"
|
||||
},
|
||||
"login": {
|
||||
"signIn": "Entrar",
|
||||
"rememberMe": "Lembrar de mim",
|
||||
"rememberMeDesc": "Permanecer conectado após fechar o navegador",
|
||||
"insecure": "Não é possível fazer login com uma conexão insegura. Use HTTPS.",
|
||||
"2faRequired": "É necessária autenticação de dois fatores",
|
||||
"2faWrong": "A autenticação de dois fatores está errada"
|
||||
},
|
||||
"client": {
|
||||
"empty": "Ainda não há clientes.",
|
||||
"newShort": "Novo",
|
||||
"sort": "Organizar",
|
||||
"create": "Criar cliente",
|
||||
"created": "Cliente criado",
|
||||
"new": "Novo cliente",
|
||||
"name": "Nome",
|
||||
"expireDate": "Data de expiração",
|
||||
"expireDateDesc": "Data em que o cliente será desativado. Em branco para permanente",
|
||||
"deleteClient": "Deletar cliente",
|
||||
"deleteDialog1": "Tem certeza de que deseja excluir?",
|
||||
"deleteDialog2": "Esta ação não pode ser desfeita.",
|
||||
"enabled": "Ativado",
|
||||
"address": "Endereço",
|
||||
"serverAllowedIps": "IPs permitidos do servidor",
|
||||
"otlDesc": "Gerar link curto e único",
|
||||
"permanent": "Permanente",
|
||||
"createdOn": "Criado em ",
|
||||
"lastSeen": "Visto pela última vez em",
|
||||
"totalDownload": "Download total: ",
|
||||
"totalUpload": "Upload total: ",
|
||||
"newClient": "Novo cliente",
|
||||
"disableClient": "Desativar cliente",
|
||||
"enableClient": "Ativar cliente",
|
||||
"noPrivKey": "Este cliente não possui uma chave privada conhecida. Não é possível criar a configuração.",
|
||||
"showQR": "Mostrar QR Code",
|
||||
"downloadConfig": "Download da configuração",
|
||||
"allowedIpsDesc": "Quais IPs serão roteados pela VPN (substitui a configuração global)",
|
||||
"serverAllowedIpsDesc": "Quais IPs o servidor irá rotear para o cliente",
|
||||
"mtuDesc": "Define a unidade máxima de transmissão (tamanho do pacote) para o túnel VPN",
|
||||
"persistentKeepaliveDesc": "Define o intervalo (em segundos) para pacotes keep-alive. 0 desabilita-o",
|
||||
"hooks": "Hooks",
|
||||
"hooksDescription": "Os hooks só funcionam com wg-quick",
|
||||
"hooksLeaveEmpty": "Somente para wg-quick. Caso contrário, deixe em branco.",
|
||||
"dnsDesc": "Os clientes do servidor DNS usarão (substitui a configuração global)",
|
||||
"notConnected": "Client não conectado",
|
||||
"endpoint": "Endpoint",
|
||||
"endpointDesc": "IP do cliente a partir do qual a conexão WireGuard é estabelecida"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Mudar",
|
||||
"cancel": "Cancelar",
|
||||
"create": "Criar"
|
||||
},
|
||||
"toast": {
|
||||
"success": "Sucesso",
|
||||
"saved": "Salvo",
|
||||
"error": "Erro"
|
||||
},
|
||||
"form": {
|
||||
"actions": "Ação",
|
||||
"save": "Salvar",
|
||||
"revert": "Reverter",
|
||||
"sectionGeneral": "Geral",
|
||||
"sectionAdvanced": "Avançado",
|
||||
"noItems": "Sem items",
|
||||
"nullNoItems": "Sem items. Usando configuração global",
|
||||
"add": "Adicionar"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "Tempo limite da sessão",
|
||||
"sessionTimeoutDesc": "Duração da sessão para Lembrar de mim (segundos)",
|
||||
"metrics": "Métricas",
|
||||
"metricsPassword": "Senha",
|
||||
"metricsPasswordDesc": "Senha do portador para o ponto final de métricas (senha ou hash argon2)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "Rota para métricas em formato JSON",
|
||||
"prometheus": "Prometheus",
|
||||
"prometheusDesc": "Rota para métricas do Prometheus"
|
||||
},
|
||||
"config": {
|
||||
"connection": "Conexão",
|
||||
"hostDesc": "Nome de host público ao qual os clientes se conectarão (invalida a configuração)",
|
||||
"portDesc": "Porta UDP pública à qual os clientes se conectarão (invalida a configuração, você provavelmente desejará alterar a porta da interface também)",
|
||||
"allowedIpsDesc": "IPs permitidos que os clientes usarão (configuração global)",
|
||||
"dnsDesc": "Os clientes do servidor DNS usarão (configuração global)",
|
||||
"mtuDesc": "Os clientes da MTU usarão (somente para novos clientes)",
|
||||
"persistentKeepaliveDesc": "Intervalo em segundos para enviar keepalives para o servidor. 0 = desabilitado (somente para novos clientes)",
|
||||
"suggest": "Sugestão",
|
||||
"suggestDesc": "Escolha um endereço IP ou nome de host para o campo Host"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "Mudar CIDR",
|
||||
"device": "Dispositivo",
|
||||
"deviceDesc": "Dispositivo Ethernet que o tráfego wireguard deve ser encaminhado",
|
||||
"mtuDesc": "MTU que o WireGuard usará",
|
||||
"portDesc": "A porta UDP que o WireGuard irá escutar (você provavelmente vai querer mudar a porta de configuração também)",
|
||||
"changeCidr": "Mudar CIDR",
|
||||
"restart": "Reiniciar interface",
|
||||
"restartDesc": "Reiniciar a interface do WireGuard",
|
||||
"restartWarn": "Tem certeza de que deseja reiniciar a interface? Isso desconectará todos os clientes.",
|
||||
"restartSuccess": "Interface reiniciada"
|
||||
},
|
||||
"introText": "Bem-vindo ao painel de administração.\n\nAqui você pode gerenciar as configurações gerais, a configuração, as configurações da interface e os hooks.\n\nComece escolhendo uma das seções na barra lateral."
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
"required": "{0} é obrigatório",
|
||||
"validNumber": "{0} deve ser um número válido",
|
||||
"validString": "{0} deve ser uma string válida",
|
||||
"validBoolean": "{0} deve ser um booleano válido",
|
||||
"validArray": "{0} deve ser uma matriz válida",
|
||||
"stringMin": "{0} deve ter pelo menos {1} caracteres",
|
||||
"numberMin": "{0} deve ter pelo menos {1}"
|
||||
},
|
||||
"client": {
|
||||
"id": "ID do cliente",
|
||||
"name": "Nome",
|
||||
"expiresAt": "Expira em",
|
||||
"address4": "Endereço IPv4",
|
||||
"address6": "Endereço IPv6",
|
||||
"serverAllowedIps": "IPs permitidos do servidor"
|
||||
},
|
||||
"user": {
|
||||
"username": "Usuário",
|
||||
"password": "Senha",
|
||||
"remember": "Lembrar",
|
||||
"name": "Nome",
|
||||
"email": "E-mail",
|
||||
"emailInvalid": "O e-mail deve ser um e-mail válido",
|
||||
"passwordMatch": "As senhas devem corresponder",
|
||||
"totpEnable": "Habilitar TOTP",
|
||||
"totpEnableTrue": "TOTP deve estar ativado",
|
||||
"totpCode": "Código TOTP"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "Host"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "Tempo limite da sessão",
|
||||
"metricsEnabled": "Métricas",
|
||||
"metricsPassword": "Senha de métricas"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "Dispositivo",
|
||||
"cidrValid": "O CIDR deve ser válido"
|
||||
},
|
||||
"otl": "Link de uso único",
|
||||
"stringMalformed": "A string está malformada",
|
||||
"body": "O corpo deve ser um objeto válido",
|
||||
"hook": "Hook",
|
||||
"enabled": "Ativado",
|
||||
"mtu": "MTU",
|
||||
"port": "Porta",
|
||||
"persistentKeepalive": "Keepalive persistente",
|
||||
"address": "Endereço IP",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "IPs permitidos",
|
||||
"file": "Arquivo"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "Аккаунт",
|
||||
"clients": "Клиенты",
|
||||
"admin": {
|
||||
"panel": "Админ панель",
|
||||
"general": "Общие",
|
||||
"config": "Конфигурация",
|
||||
"interface": "Интерфейс",
|
||||
"hooks": "Хуки"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "E-Mail"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "Текущий пароль",
|
||||
"enable2fa": "Включить двухфакторную аутентификацию",
|
||||
"enable2faDesc": "Отсканируйте QR-код приложением-аутентификатором или введите ключ вручную.",
|
||||
"2faKey": "TOTP-ключ",
|
||||
"2faCodeDesc": "Введите код из приложения-аутентификатора.",
|
||||
"disable2fa": "Отключить двухфакторную аутентификацию",
|
||||
"disable2faDesc": "Введите пароль, чтобы отключить двухфакторную аутентификацию"
|
||||
},
|
||||
"general": {
|
||||
"name": "Имя",
|
||||
"username": "Имя пользователя",
|
||||
"password": "Пароль",
|
||||
"newPassword": "Новый пароль",
|
||||
"updatePassword": "Обновить пароль",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "Разрешённые IP",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "Постоянный keepalive",
|
||||
"logout": "Выйти",
|
||||
"continue": "Продолжить",
|
||||
"host": "Хост",
|
||||
"port": "Порт",
|
||||
"yes": "Да",
|
||||
"no": "Нет",
|
||||
"confirmPassword": "Подтвердите пароль",
|
||||
"loading": "Загрузка...",
|
||||
"2fa": "Двухфакторная аутентификация",
|
||||
"2faCode": "TOTP‑код"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "Добро пожаловать в первичную настройку wg-easy",
|
||||
"welcomeDesc": "Вы нашли самый простой способ установить и управлять WireGuard на любом Linux-хосте",
|
||||
"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": "Дата, когда клиент будет отключён. Пусто — бессрочно",
|
||||
"deleteClient": "Удалить клиента",
|
||||
"deleteDialog1": "Вы уверены, что хотите удалить",
|
||||
"deleteDialog2": "Это действие необратимо.",
|
||||
"enabled": "Включен",
|
||||
"address": "Адрес",
|
||||
"serverAllowedIps": "Разрешённые IP сервера",
|
||||
"otlDesc": "Сгенерировать одноразовую короткую ссылку",
|
||||
"permanent": "Постоянный",
|
||||
"createdOn": "Создан ",
|
||||
"lastSeen": "Последнее подключение ",
|
||||
"totalDownload": "Всего загружено: ",
|
||||
"totalUpload": "Всего отправлено: ",
|
||||
"newClient": "Новый клиент",
|
||||
"disableClient": "Отключить клиента",
|
||||
"enableClient": "Включить клиента",
|
||||
"noPrivKey": "У этого клиента нет приватного ключа. Невозможно создать конфигурацию.",
|
||||
"showQR": "Показать QR‑код",
|
||||
"downloadConfig": "Скачать конфигурацию",
|
||||
"allowedIpsDesc": "Какие IP будут маршрутизироваться через VPN (перезаписывает общую конфигурацию)",
|
||||
"serverAllowedIpsDesc": "Какие IP сервер будет отправлять клиенту",
|
||||
"mtuDesc": "Максимальный размер пакета для VPN‑туннеля",
|
||||
"persistentKeepaliveDesc": "Интервал пакетов для поддержания соединения (в секундах). 0 — отключено.",
|
||||
"hooks": "Хуки",
|
||||
"hooksDescription": "Хуки работают только с wg-quick",
|
||||
"hooksLeaveEmpty": "Только для wg-quick. Иначе оставьте пустым",
|
||||
"dnsDesc": "DNS‑сервер, который будут использовать клиенты (перезаписывает общую конфигурацию)",
|
||||
"notConnected": "Клиент не подключен",
|
||||
"endpoint": "Конечная точка",
|
||||
"endpointDesc": "IP-адрес клиента, с которого установлено соединение WireGuard"
|
||||
},
|
||||
"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‑адрес или имя хоста для поля Host"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "CIDR изменён",
|
||||
"device": "Устройство",
|
||||
"deviceDesc": "Сетевое устройство, через которое должен проходить трафик WireGuard",
|
||||
"mtuDesc": "MTU, который использует WireGuard",
|
||||
"portDesc": "UDP‑порт, на котором WireGuard будет слушать (возможно, нужно изменить и порт конфигурации)",
|
||||
"changeCidr": "Изменить CIDR",
|
||||
"restart": "Перезапустить интерфейс",
|
||||
"restartDesc": "Перезапустить интерфейс WireGuard",
|
||||
"restartWarn": "Вы уверены, что хотите перезапустить интерфейс? Все клиенты будут отключены.",
|
||||
"restartSuccess": "Интерфейс перезапущен"
|
||||
},
|
||||
"introText": "Добро пожаловать в панель администратора.\n\nЗдесь вы можете управлять общими настройками, конфигурацией, параметрами интерфейса и хуками.\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": "Email",
|
||||
"emailInvalid": "Email должен быть валидным",
|
||||
"passwordMatch": "Пароли должны совпадать",
|
||||
"totpEnable": "Включить TOTP",
|
||||
"totpEnableTrue": "Необходимо включить TOTP",
|
||||
"totpCode": "TOTP‑код"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "Хост"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "Тайм-аут сессии",
|
||||
"metricsEnabled": "Метрики",
|
||||
"metricsPassword": "Пароль для метрик"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "Устройство",
|
||||
"cidrValid": "CIDR должен быть валидным"
|
||||
},
|
||||
"otl": "Одноразовая ссылка",
|
||||
"stringMalformed": "Строка имеет неверный формат",
|
||||
"body": "Тело должно быть объектом",
|
||||
"hook": "Хук",
|
||||
"enabled": "Включено",
|
||||
"mtu": "MTU",
|
||||
"port": "Порт",
|
||||
"persistentKeepalive": "Поддерживать соединение",
|
||||
"address": "IP‑адрес",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "Разрешённые IP",
|
||||
"file": "Файл"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"pages": {
|
||||
"me": "Hesap",
|
||||
"clients": "İstemciler",
|
||||
"admin": {
|
||||
"panel": "Yönetici Paneli",
|
||||
"general": "Genel",
|
||||
"config": "Yapılandırma",
|
||||
"interface": "Arayüz",
|
||||
"hooks": "Hook'lar"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"email": "E-Posta"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "Mevcut Şifre",
|
||||
"enable2fa": "İki Faktörlü Kimlik Doğrulamayı Etkinleştir",
|
||||
"enable2faDesc": "QR kodunu kimlik doğrulayıcı uygulamanızla tarayın veya anahtarı manuel olarak girin.",
|
||||
"2faKey": "TOTP Anahtarı",
|
||||
"2faCodeDesc": "Kimlik doğrulayıcı uygulamanızdan kodu girin.",
|
||||
"disable2fa": "İki Faktörlü Kimlik Doğrulamayı Devre Dışı Bırak",
|
||||
"disable2faDesc": "İki Faktörlü Kimlik Doğrulamayı devre dışı bırakmak için şifrenizi girin."
|
||||
},
|
||||
"general": {
|
||||
"name": "Ad",
|
||||
"username": "Kullanıcı Adı",
|
||||
"password": "Şifre",
|
||||
"newPassword": "Yeni Şifre",
|
||||
"updatePassword": "Şifreyi Güncelle",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "İzin Verilen IP'ler",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "Kalıcı Keepalive",
|
||||
"logout": "Çıkış Yap",
|
||||
"continue": "Devam Et",
|
||||
"host": "Ana Bilgisayar",
|
||||
"port": "Port",
|
||||
"yes": "Evet",
|
||||
"no": "Hayır",
|
||||
"confirmPassword": "Şifreyi Onayla",
|
||||
"loading": "Yükleniyor...",
|
||||
"2fa": "İki Faktörlü Kimlik Doğrulama",
|
||||
"2faCode": "TOTP Kodu"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "wg-easy ilk kurulumunuza hoş geldiniz",
|
||||
"welcomeDesc": "Herhangi bir Linux ana bilgisayarda WireGuard kurmanın ve yönetmenin en kolay yolunu buldunuz",
|
||||
"existingSetup": "Mevcut bir kurulumunuz var mı?",
|
||||
"createAdminDesc": "Lütfen önce bir yönetici kullanıcı adı ve güçlü bir güvenli şifre girin. Bu bilgiler yönetim panelinize giriş yapmak için kullanılacaktır.",
|
||||
"setupConfigDesc": "Lütfen ana bilgisayar ve port bilgilerini girin. Bu, cihazlarında WireGuard kurulumu yaparken istemci yapılandırması için kullanılacaktır.",
|
||||
"setupMigrationDesc": "Verilerinizi önceki wg-easy sürümünüzden yeni kurulumunuza taşımak istiyorsanız yedekleme dosyasını sağlayın.",
|
||||
"upload": "Yükle",
|
||||
"migration": "Yedeği geri yükle:",
|
||||
"createAccount": "Hesap Oluştur",
|
||||
"successful": "Kurulum başarılı",
|
||||
"hostDesc": "İstemcilerin bağlanacağı genel ana bilgisayar adı",
|
||||
"portDesc": "İstemcilerin bağlanacağı ve WireGuard'ın dinleyeceği genel UDP portu"
|
||||
},
|
||||
"update": {
|
||||
"updateAvailable": "Güncelleme mevcut!",
|
||||
"update": "Güncelle"
|
||||
},
|
||||
"theme": {
|
||||
"dark": "Koyu tema",
|
||||
"light": "Açık tema",
|
||||
"system": "Sistem teması"
|
||||
},
|
||||
"layout": {
|
||||
"toggleCharts": "Grafikleri Göster/Gizle",
|
||||
"donate": "Bağış Yap"
|
||||
},
|
||||
"login": {
|
||||
"signIn": "Giriş Yap",
|
||||
"rememberMe": "Beni hatırla",
|
||||
"rememberMeDesc": "Tarayıcıyı kapattıktan sonra giriş yapmış olarak kal",
|
||||
"insecure": "Güvensiz bir bağlantı ile giriş yapamazsınız. HTTPS kullanın.",
|
||||
"2faRequired": "İki Faktörlü Kimlik Doğrulama gerekli",
|
||||
"2faWrong": "İki Faktörlü Kimlik Doğrulama yanlış"
|
||||
},
|
||||
"client": {
|
||||
"empty": "Henüz istemci yok.",
|
||||
"newShort": "Yeni",
|
||||
"sort": "Sırala",
|
||||
"create": "İstemci Oluştur",
|
||||
"created": "İstemci oluşturuldu",
|
||||
"new": "Yeni İstemci",
|
||||
"name": "Ad",
|
||||
"expireDate": "Son Kullanma Tarihi",
|
||||
"expireDateDesc": "İstemcinin devre dışı bırakılacağı tarih. Kalıcı için boş bırakın",
|
||||
"deleteClient": "İstemciyi Sil",
|
||||
"deleteDialog1": "Silmek istediğinizden emin misiniz",
|
||||
"deleteDialog2": "Bu eylem geri alınamaz.",
|
||||
"enabled": "Etkin",
|
||||
"address": "Adres",
|
||||
"serverAllowedIps": "Sunucu İzin Verilen IP'ler",
|
||||
"otlDesc": "Kısa tek seferlik bağlantı oluştur",
|
||||
"permanent": "Kalıcı",
|
||||
"createdOn": "Oluşturulma tarihi ",
|
||||
"lastSeen": "Son görülme ",
|
||||
"totalDownload": "Toplam İndirme: ",
|
||||
"totalUpload": "Toplam Yükleme: ",
|
||||
"newClient": "Yeni İstemci",
|
||||
"disableClient": "İstemciyi Devre Dışı Bırak",
|
||||
"enableClient": "İstemciyi Etkinleştir",
|
||||
"noPrivKey": "Bu istemcinin bilinen özel anahtarı yok. Yapılandırma oluşturulamıyor.",
|
||||
"showQR": "QR Kodunu Göster",
|
||||
"downloadConfig": "Yapılandırmayı İndir",
|
||||
"allowedIpsDesc": "Hangi IP'lerin VPN üzerinden yönlendirileceği (genel yapılandırmayı geçersiz kılar)",
|
||||
"serverAllowedIpsDesc": "Sunucunun istemciye yönlendireceği IP'ler",
|
||||
"mtuDesc": "VPN tüneli için maksimum iletim birimini (paket boyutu) ayarlar",
|
||||
"persistentKeepaliveDesc": "Keepalive paketleri için aralığı (saniye cinsinden) ayarlar. 0 devre dışı bırakır",
|
||||
"hooks": "Hook'lar",
|
||||
"hooksDescription": "Hook'lar sadece wg-quick ile çalışır",
|
||||
"hooksLeaveEmpty": "Sadece wg-quick için. Aksi takdirde boş bırakın",
|
||||
"dnsDesc": "İstemcilerin kullanacağı DNS sunucusu (genel yapılandırmayı geçersiz kılar)",
|
||||
"notConnected": "İstemci bağlı değil",
|
||||
"endpoint": "Uç Nokta",
|
||||
"endpointDesc": "WireGuard bağlantısının kurulduğu istemcinin IP'si"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Değiştir",
|
||||
"cancel": "İptal",
|
||||
"create": "Oluştur"
|
||||
},
|
||||
"toast": {
|
||||
"success": "Başarılı",
|
||||
"saved": "Kaydedildi",
|
||||
"error": "Hata"
|
||||
},
|
||||
"form": {
|
||||
"actions": "Eylemler",
|
||||
"save": "Kaydet",
|
||||
"revert": "Geri Al",
|
||||
"sectionGeneral": "Genel",
|
||||
"sectionAdvanced": "Gelişmiş",
|
||||
"noItems": "Öğe yok",
|
||||
"nullNoItems": "Öğe yok. Genel yapılandırma kullanılıyor",
|
||||
"add": "Ekle"
|
||||
},
|
||||
"admin": {
|
||||
"general": {
|
||||
"sessionTimeout": "Oturum Zaman Aşımı",
|
||||
"sessionTimeoutDesc": "Beni Hatırla için oturum süresi (saniye)",
|
||||
"metrics": "Metrikler",
|
||||
"metricsPassword": "Şifre",
|
||||
"metricsPasswordDesc": "Metrik uç noktası için Bearer şifresi (şifre veya argon2 hash)",
|
||||
"json": "JSON",
|
||||
"jsonDesc": "JSON formatında metrikler için rota",
|
||||
"prometheus": "Prometheus",
|
||||
"prometheusDesc": "Prometheus metrikleri için rota"
|
||||
},
|
||||
"config": {
|
||||
"connection": "Bağlantı",
|
||||
"hostDesc": "İstemcilerin bağlanacağı genel ana bilgisayar adı (yapılandırmayı geçersiz kılar)",
|
||||
"portDesc": "İstemcilerin bağlanacağı genel UDP portu (yapılandırmayı geçersiz kılar, muhtemelen Arayüz Portunu da değiştirmek isteyeceksiniz)",
|
||||
"allowedIpsDesc": "İstemcilerin kullanacağı İzin Verilen IP'ler (genel yapılandırma)",
|
||||
"dnsDesc": "İstemcilerin kullanacağı DNS sunucusu (genel yapılandırma)",
|
||||
"mtuDesc": "İstemcilerin kullanacağı MTU (sadece yeni istemciler için)",
|
||||
"persistentKeepaliveDesc": "Sunucuya keepalive göndermek için saniye cinsinden aralık. 0 = devre dışı (sadece yeni istemciler için)",
|
||||
"suggest": "Öner",
|
||||
"suggestDesc": "Ana Bilgisayar alanı için bir IP Adresi veya Ana Bilgisayar Adı seçin"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "CIDR değiştirildi",
|
||||
"device": "Cihaz",
|
||||
"deviceDesc": "WireGuard trafiğinin yönlendirileceği Ethernet cihazı",
|
||||
"mtuDesc": "WireGuard'ın kullanacağı MTU",
|
||||
"portDesc": "WireGuard'ın dinleyeceği UDP Portu (muhtemelen Yapılandırma Portunu da değiştirmek isteyeceksiniz)",
|
||||
"changeCidr": "CIDR'ı Değiştir",
|
||||
"restart": "Arayüzü Yeniden Başlat",
|
||||
"restartDesc": "WireGuard arayüzünü yeniden başlat",
|
||||
"restartWarn": "Arayüzü yeniden başlatmak istediğinizden emin misiniz? Bu tüm istemcilerin bağlantısını kesecektir.",
|
||||
"restartSuccess": "Arayüz yeniden başlatıldı"
|
||||
},
|
||||
"introText": "Yönetici paneline hoş geldiniz.\n\nBurada genel ayarları, yapılandırmayı, arayüz ayarlarını ve hook'ları yönetebilirsiniz.\n\nKenar çubuğundaki bölümlerden birini seçerek başlayın."
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
"required": "{0} gerekli",
|
||||
"validNumber": "{0} geçerli bir sayı olmalı",
|
||||
"validString": "{0} geçerli bir dize olmalı",
|
||||
"validBoolean": "{0} geçerli bir boolean olmalı",
|
||||
"validArray": "{0} geçerli bir dizi olmalı",
|
||||
"stringMin": "{0} en az {1} karakter olmalı",
|
||||
"numberMin": "{0} en az {1} olmalı"
|
||||
},
|
||||
"client": {
|
||||
"id": "İstemci ID",
|
||||
"name": "Ad",
|
||||
"expiresAt": "Son Kullanma Tarihi",
|
||||
"address4": "IPv4 Adresi",
|
||||
"address6": "IPv6 Adresi",
|
||||
"serverAllowedIps": "Sunucu İzin Verilen IP'ler"
|
||||
},
|
||||
"user": {
|
||||
"username": "Kullanıcı Adı",
|
||||
"password": "Şifre",
|
||||
"remember": "Hatırla",
|
||||
"name": "Ad",
|
||||
"email": "E-posta",
|
||||
"emailInvalid": "E-posta geçerli bir e-posta olmalı",
|
||||
"passwordMatch": "Şifreler eşleşmeli",
|
||||
"totpEnable": "TOTP Etkinleştir",
|
||||
"totpEnableTrue": "TOTP Etkinleştir doğru olmalı",
|
||||
"totpCode": "TOTP Kodu"
|
||||
},
|
||||
"userConfig": {
|
||||
"host": "Ana Bilgisayar"
|
||||
},
|
||||
"general": {
|
||||
"sessionTimeout": "Oturum Zaman Aşımı",
|
||||
"metricsEnabled": "Metrikler",
|
||||
"metricsPassword": "Metrik Şifresi"
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "Cihaz",
|
||||
"cidrValid": "CIDR geçerli olmalı"
|
||||
},
|
||||
"otl": "Tek seferlik bağlantı",
|
||||
"stringMalformed": "Dize hatalı biçimlendirilmiş",
|
||||
"body": "Gövde geçerli bir nesne olmalı",
|
||||
"hook": "Hook",
|
||||
"enabled": "Etkin",
|
||||
"mtu": "MTU",
|
||||
"port": "Port",
|
||||
"persistentKeepalive": "Kalıcı Keepalive",
|
||||
"address": "IP Adresi",
|
||||
"dns": "DNS",
|
||||
"allowedIps": "İzin Verilen IP'ler",
|
||||
"file": "Dosya"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,10 @@
|
||||
"hooks": "Hooks",
|
||||
"hooksDescription": "Hooks працюють лише з wg-quick",
|
||||
"hooksLeaveEmpty": "Тільки для wg-quick. Інакше залиште порожнім",
|
||||
"dnsDesc": "DNS сервер, який використовуватимуть клієнти (перевизначає глобальну конфігурацію)"
|
||||
"dnsDesc": "DNS сервер, який використовуватимуть клієнти (перевизначає глобальну конфігурацію)",
|
||||
"notConnected": "Клієнт не підключений",
|
||||
"endpoint": "Кінцева точка",
|
||||
"endpointDesc": "IP-адреса клієнта, з якої встановлюється з’єднання WireGuard"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Змінити",
|
||||
|
||||
@@ -112,7 +112,8 @@
|
||||
"persistentKeepaliveDesc": "设置保活数据包的发送间隔(秒)。0表示禁用",
|
||||
"hooks": "钩子脚本",
|
||||
"hooksDescription": "钩子脚本仅在使用wg-quick时有效",
|
||||
"hooksLeaveEmpty": "如果不使用wg-quick,请留空此字段"
|
||||
"hooksLeaveEmpty": "如果不使用wg-quick,请留空此字段",
|
||||
"search": "搜索客户端..."
|
||||
},
|
||||
"dialog": {
|
||||
"change": "确认修改",
|
||||
@@ -154,7 +155,7 @@
|
||||
"dnsDesc": "客户端使用的全局DNS设置",
|
||||
"mtuDesc": "新客户端将使用的MTU(仅影响新客户端)",
|
||||
"persistentKeepaliveDesc": "向服务器发送保活数据包的间隔秒数。0表示禁用(仅影响新客户端)",
|
||||
"suggest": "自动检测",
|
||||
"suggest": "检测",
|
||||
"suggestDesc": "为'主机'字段选择IP地址或主机名"
|
||||
},
|
||||
"interface": {
|
||||
@@ -169,7 +170,7 @@
|
||||
"restartWarn": "确定要重启接口吗?这将断开所有客户端的连接。",
|
||||
"restartSuccess": "接口重启成功"
|
||||
},
|
||||
"introText": "欢迎使用管理控制台。\n\n您可以在这里管理通用设置、网络配置、接口设置和钩子脚本。\n\n请从侧边栏选择一个功能模块开始。"
|
||||
"introText": "欢迎使用管理控制台。\n\n您可以在这里管理通用设置、网络配置、接口配置和钩子脚本。\n\n请从侧边栏选择一个功能模块开始。"
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
|
||||
@@ -113,7 +113,8 @@
|
||||
"hooks": "掛鉤",
|
||||
"hooksDescription": "掛鉤僅適用於wg-quick",
|
||||
"hooksLeaveEmpty": "僅適用於wg-quick,否則請留空",
|
||||
"dnsDesc": "客戶端使用的域名系統伺服器(取代全局配置)"
|
||||
"dnsDesc": "客戶端使用的域名系統伺服器(取代全局配置)",
|
||||
"search": "搜尋客戶端..."
|
||||
},
|
||||
"dialog": {
|
||||
"change": "更改",
|
||||
|
||||
+61
-19
@@ -13,6 +13,7 @@ export default defineNuxtConfig({
|
||||
'@pinia/nuxt',
|
||||
'@eschricht/nuxt-color-mode',
|
||||
'radix-vue/nuxt',
|
||||
'@vueuse/nuxt',
|
||||
'@nuxt/eslint',
|
||||
],
|
||||
colorMode: {
|
||||
@@ -26,39 +27,83 @@ export default defineNuxtConfig({
|
||||
experimental: {
|
||||
localeDetector: './localeDetector.ts',
|
||||
},
|
||||
// https://wg-easy.github.io/wg-easy/latest/contributing/translation/
|
||||
locales: [
|
||||
{
|
||||
// same as i18n.config.ts
|
||||
code: 'en',
|
||||
// BCP 47 language tag
|
||||
language: 'en-US',
|
||||
name: 'English',
|
||||
},
|
||||
{
|
||||
code: 'uk',
|
||||
language: 'uk-UA',
|
||||
name: 'Українська',
|
||||
},
|
||||
{
|
||||
code: 'fr',
|
||||
language: 'fr-FR',
|
||||
name: 'Français',
|
||||
},
|
||||
{
|
||||
code: 'de',
|
||||
language: 'de-DE',
|
||||
name: 'Deutsch',
|
||||
},
|
||||
{
|
||||
code: 'zh-HK',
|
||||
language: 'zh-HK',
|
||||
name: '繁體中文(香港)',
|
||||
code: 'es',
|
||||
language: 'es-ES',
|
||||
name: 'Español',
|
||||
},
|
||||
{
|
||||
code: 'it',
|
||||
language: 'it-IT',
|
||||
name: 'Italiano',
|
||||
},
|
||||
{
|
||||
code: 'fr',
|
||||
language: 'fr-FR',
|
||||
name: 'Français',
|
||||
},
|
||||
{
|
||||
code: 'ko',
|
||||
language: 'ko-KR',
|
||||
name: '한국어',
|
||||
},
|
||||
{
|
||||
code: 'ru',
|
||||
language: 'ru-RU',
|
||||
name: 'Русский',
|
||||
},
|
||||
{
|
||||
code: 'uk',
|
||||
language: 'uk-UA',
|
||||
name: 'Українська',
|
||||
},
|
||||
{
|
||||
code: 'zh-CN',
|
||||
language: 'zh-CN',
|
||||
name: '简体中文',
|
||||
},
|
||||
{
|
||||
code: 'zh-HK',
|
||||
language: 'zh-HK',
|
||||
name: '繁體中文(香港)',
|
||||
},
|
||||
{
|
||||
code: 'pl',
|
||||
language: 'pl-PL',
|
||||
name: 'Polski',
|
||||
},
|
||||
{
|
||||
code: 'pt-BR',
|
||||
language: 'pt-BR',
|
||||
name: 'Português (Brasil)',
|
||||
},
|
||||
{
|
||||
code: 'tr',
|
||||
language: 'tr-TR',
|
||||
name: 'Türkçe',
|
||||
},
|
||||
{
|
||||
code: 'bn',
|
||||
language: 'bn-BD',
|
||||
name: 'বাংলা',
|
||||
},
|
||||
{
|
||||
code: 'id',
|
||||
language: 'id-ID',
|
||||
name: 'Bahasa Indonesia',
|
||||
},
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
vueI18n: './i18n.config.ts',
|
||||
@@ -66,15 +111,12 @@ export default defineNuxtConfig({
|
||||
detectBrowserLanguage: {
|
||||
useCookie: true,
|
||||
},
|
||||
bundle: {
|
||||
optimizeTranslationDirective: false,
|
||||
},
|
||||
},
|
||||
nitro: {
|
||||
esbuild: {
|
||||
options: {
|
||||
// to support big int
|
||||
target: 'es2020',
|
||||
target: 'node20',
|
||||
},
|
||||
},
|
||||
alias: {
|
||||
|
||||
+32
-30
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wg-easy",
|
||||
"version": "15.1.0",
|
||||
"version": "15.2.0-beta.1",
|
||||
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -20,52 +20,54 @@
|
||||
"cli:dev": "tsx cli/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eschricht/nuxt-color-mode": "^1.1.5",
|
||||
"@eschricht/nuxt-color-mode": "^1.2.0",
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@libsql/client": "^0.15.9",
|
||||
"@nuxtjs/i18n": "^9.5.6",
|
||||
"@libsql/client": "^0.15.15",
|
||||
"@nuxtjs/i18n": "^10.2.0",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
"@phc/format": "^1.0.0",
|
||||
"@pinia/nuxt": "^0.11.1",
|
||||
"@pinia/nuxt": "^0.11.3",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"apexcharts": "^4.7.0",
|
||||
"argon2": "^0.43.0",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"@vueuse/nuxt": "^14.0.0",
|
||||
"apexcharts": "^5.3.6",
|
||||
"argon2": "^0.44.0",
|
||||
"cidr-tools": "^11.0.3",
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.2",
|
||||
"crc-32": "^1.2.2",
|
||||
"debug": "^4.4.1",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"ip-bigint": "^8.2.1",
|
||||
"is-cidr": "^5.1.1",
|
||||
"debug": "^4.4.3",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"ip-bigint": "^8.2.2",
|
||||
"is-cidr": "^6.0.1",
|
||||
"is-ip": "^5.0.1",
|
||||
"js-sha256": "^0.11.1",
|
||||
"nuxt": "^3.17.5",
|
||||
"otpauth": "^9.4.0",
|
||||
"pinia": "^3.0.3",
|
||||
"qr": "^0.5.0",
|
||||
"nuxt": "^3.20.1",
|
||||
"otpauth": "^9.4.1",
|
||||
"pinia": "^3.0.4",
|
||||
"qr": "^0.5.2",
|
||||
"radix-vue": "^1.9.17",
|
||||
"semver": "^7.7.2",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"semver": "^7.7.3",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"timeago.js": "^4.0.2",
|
||||
"vue": "latest",
|
||||
"vue3-apexcharts": "^1.8.0",
|
||||
"zod": "^3.25.67"
|
||||
"vue3-apexcharts": "^1.10.0",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint": "^1.4.1",
|
||||
"@nuxt/eslint": "^1.10.0",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/phc__format": "^1.0.1",
|
||||
"@types/semver": "^7.7.0",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.30.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"@types/semver": "^7.7.1",
|
||||
"drizzle-kit": "^0.31.6",
|
||||
"esbuild": "^0.27.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.13",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vue-tsc": "^2.2.10"
|
||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
||||
"tsx": "^4.20.6",
|
||||
"typescript": "^5.9.3",
|
||||
"vue-tsc": "^3.1.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.12.4"
|
||||
"packageManager": "pnpm@10.21.0"
|
||||
}
|
||||
|
||||
Generated
+3603
-3457
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@ export default definePermissionEventHandler(
|
||||
event,
|
||||
validateZod(InterfaceCidrUpdateSchema, event)
|
||||
);
|
||||
|
||||
await Database.interfaces.updateCidr(data);
|
||||
await WireGuard.saveConfig();
|
||||
return { success: true };
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
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,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -19,19 +19,14 @@ export default definePermissionEventHandler(
|
||||
}
|
||||
|
||||
const config = await WireGuard.getClientConfiguration({ clientId });
|
||||
const configName = client.name
|
||||
.replace(/[^a-zA-Z0-9_=+.-]/g, '-')
|
||||
.replace(/(-{2,}|-$)/g, '-')
|
||||
.replace(/-$/, '')
|
||||
.substring(0, 32);
|
||||
|
||||
setHeader(
|
||||
event,
|
||||
'Content-Disposition',
|
||||
`attachment; filename="${configName || clientId}.conf"`
|
||||
`attachment; filename="${WireGuard.cleanClientFilename(client.name) || clientId}.conf"`
|
||||
);
|
||||
|
||||
setHeader(event, 'Content-Type', 'text/plain');
|
||||
setHeader(event, 'Content-Type', 'application/octet-stream');
|
||||
return config;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -18,6 +18,13 @@ export default definePermissionEventHandler(
|
||||
statusMessage: 'Client not found',
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
||||
// data can be undefined if the client is disabled
|
||||
const data = await WireGuard.dumpByPublicKey(result.publicKey);
|
||||
|
||||
return {
|
||||
...result,
|
||||
endpoint: data?.endpoint,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
export default definePermissionEventHandler('clients', 'custom', ({ user }) => {
|
||||
import { ClientQuerySchema } from '#db/repositories/client/types';
|
||||
|
||||
export default definePermissionEventHandler(
|
||||
'clients',
|
||||
'custom',
|
||||
async ({ event, user }) => {
|
||||
const { filter } = await getValidatedQuery(
|
||||
event,
|
||||
validateZod(ClientQuerySchema, event)
|
||||
);
|
||||
|
||||
if (user.role === roles.ADMIN) {
|
||||
return WireGuard.getAllClients();
|
||||
return WireGuard.getAllClients(filter);
|
||||
}
|
||||
return WireGuard.getClientsForUser(user.id);
|
||||
});
|
||||
return WireGuard.getClientsForUser(user.id, filter);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -9,8 +9,10 @@ export default definePermissionEventHandler(
|
||||
validateZod(ClientCreateSchema, event)
|
||||
);
|
||||
|
||||
await Database.clients.create({ name, expiresAt });
|
||||
const result = await Database.clients.create({ name, expiresAt });
|
||||
await WireGuard.saveConfig();
|
||||
return { success: true };
|
||||
|
||||
const clientId = result[0]!.clientId;
|
||||
return { success: true, clientId };
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,10 +4,13 @@ export default defineEventHandler(async () => {
|
||||
const latestRelease = await cachedFetchLatestRelease();
|
||||
const updateAvailable = gt(latestRelease.version, RELEASE);
|
||||
const insecure = WG_ENV.INSECURE;
|
||||
const isAwg = WG_ENV.WG_EXECUTABLE === 'awg';
|
||||
|
||||
return {
|
||||
currentRelease: RELEASE,
|
||||
latestRelease: latestRelease,
|
||||
updateAvailable,
|
||||
insecure,
|
||||
isAwg,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,6 +8,19 @@ 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 };
|
||||
}
|
||||
|
||||
return { success: true, setupDone: setupDone };
|
||||
});
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
ALTER TABLE `clients_table` ADD `j_c` integer;--> statement-breakpoint
|
||||
ALTER TABLE `clients_table` ADD `j_min` integer;--> statement-breakpoint
|
||||
ALTER TABLE `clients_table` ADD `j_max` integer;--> statement-breakpoint
|
||||
ALTER TABLE `clients_table` ADD `i1` text;--> statement-breakpoint
|
||||
ALTER TABLE `clients_table` ADD `i2` text;--> statement-breakpoint
|
||||
ALTER TABLE `clients_table` ADD `i3` text;--> statement-breakpoint
|
||||
ALTER TABLE `clients_table` ADD `i4` text;--> statement-breakpoint
|
||||
ALTER TABLE `clients_table` ADD `i5` text;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `j_c` integer DEFAULT 7;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `j_min` integer DEFAULT 10;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `j_max` integer DEFAULT 1000;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `s1` integer DEFAULT 128;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `s2` integer DEFAULT 56;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `s3` integer;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `s4` integer;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `i1` text;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `i2` text;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `i3` text;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `i4` text;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `i5` text;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `h1` integer DEFAULT 0;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `h2` integer DEFAULT 0;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `h3` integer DEFAULT 0;--> statement-breakpoint
|
||||
ALTER TABLE `interfaces_table` ADD `h4` integer DEFAULT 0;--> statement-breakpoint
|
||||
ALTER TABLE `user_configs_table` ADD `default_j_c` integer DEFAULT 7;--> statement-breakpoint
|
||||
ALTER TABLE `user_configs_table` ADD `default_j_min` integer DEFAULT 10;--> statement-breakpoint
|
||||
ALTER TABLE `user_configs_table` ADD `default_j_max` integer DEFAULT 1000;--> statement-breakpoint
|
||||
ALTER TABLE `user_configs_table` ADD `default_i1` text;--> statement-breakpoint
|
||||
ALTER TABLE `user_configs_table` ADD `default_i2` text;--> statement-breakpoint
|
||||
ALTER TABLE `user_configs_table` ADD `default_i3` text;--> statement-breakpoint
|
||||
ALTER TABLE `user_configs_table` ADD `default_i4` text;--> statement-breakpoint
|
||||
ALTER TABLE `user_configs_table` ADD `default_i5` text;
|
||||
@@ -0,0 +1,976 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "e09bc17a-dab6-45a3-a09c-57af222b08fb",
|
||||
"prevId": "78de2e52-c4a8-4900-86c5-92f34739623a",
|
||||
"tables": {
|
||||
"clients_table": {
|
||||
"name": "clients_table",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"interface_id": {
|
||||
"name": "interface_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"ipv4_address": {
|
||||
"name": "ipv4_address",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"ipv6_address": {
|
||||
"name": "ipv6_address",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pre_up": {
|
||||
"name": "pre_up",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"post_up": {
|
||||
"name": "post_up",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"pre_down": {
|
||||
"name": "pre_down",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"post_down": {
|
||||
"name": "post_down",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"private_key": {
|
||||
"name": "private_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"public_key": {
|
||||
"name": "public_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pre_shared_key": {
|
||||
"name": "pre_shared_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"allowed_ips": {
|
||||
"name": "allowed_ips",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"server_allowed_ips": {
|
||||
"name": "server_allowed_ips",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"persistent_keepalive": {
|
||||
"name": "persistent_keepalive",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"mtu": {
|
||||
"name": "mtu",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"j_c": {
|
||||
"name": "j_c",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"j_min": {
|
||||
"name": "j_min",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"j_max": {
|
||||
"name": "j_max",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i1": {
|
||||
"name": "i1",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i2": {
|
||||
"name": "i2",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i3": {
|
||||
"name": "i3",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i4": {
|
||||
"name": "i4",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i5": {
|
||||
"name": "i5",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"dns": {
|
||||
"name": "dns",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"server_endpoint": {
|
||||
"name": "server_endpoint",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"clients_table_ipv4_address_unique": {
|
||||
"name": "clients_table_ipv4_address_unique",
|
||||
"columns": [
|
||||
"ipv4_address"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"clients_table_ipv6_address_unique": {
|
||||
"name": "clients_table_ipv6_address_unique",
|
||||
"columns": [
|
||||
"ipv6_address"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"clients_table_user_id_users_table_id_fk": {
|
||||
"name": "clients_table_user_id_users_table_id_fk",
|
||||
"tableFrom": "clients_table",
|
||||
"tableTo": "users_table",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "restrict",
|
||||
"onUpdate": "cascade"
|
||||
},
|
||||
"clients_table_interface_id_interfaces_table_name_fk": {
|
||||
"name": "clients_table_interface_id_interfaces_table_name_fk",
|
||||
"tableFrom": "clients_table",
|
||||
"tableTo": "interfaces_table",
|
||||
"columnsFrom": [
|
||||
"interface_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"name"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"general_table": {
|
||||
"name": "general_table",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 1
|
||||
},
|
||||
"setup_step": {
|
||||
"name": "setup_step",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"session_password": {
|
||||
"name": "session_password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"session_timeout": {
|
||||
"name": "session_timeout",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"metrics_prometheus": {
|
||||
"name": "metrics_prometheus",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"metrics_json": {
|
||||
"name": "metrics_json",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"metrics_password": {
|
||||
"name": "metrics_password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"hooks_table": {
|
||||
"name": "hooks_table",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pre_up": {
|
||||
"name": "pre_up",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"post_up": {
|
||||
"name": "post_up",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pre_down": {
|
||||
"name": "pre_down",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"post_down": {
|
||||
"name": "post_down",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"hooks_table_id_interfaces_table_name_fk": {
|
||||
"name": "hooks_table_id_interfaces_table_name_fk",
|
||||
"tableFrom": "hooks_table",
|
||||
"tableTo": "interfaces_table",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"name"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"interfaces_table": {
|
||||
"name": "interfaces_table",
|
||||
"columns": {
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"device": {
|
||||
"name": "device",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"port": {
|
||||
"name": "port",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"private_key": {
|
||||
"name": "private_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"public_key": {
|
||||
"name": "public_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"ipv4_cidr": {
|
||||
"name": "ipv4_cidr",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"ipv6_cidr": {
|
||||
"name": "ipv6_cidr",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"mtu": {
|
||||
"name": "mtu",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"j_c": {
|
||||
"name": "j_c",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 7
|
||||
},
|
||||
"j_min": {
|
||||
"name": "j_min",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 10
|
||||
},
|
||||
"j_max": {
|
||||
"name": "j_max",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 1000
|
||||
},
|
||||
"s1": {
|
||||
"name": "s1",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 128
|
||||
},
|
||||
"s2": {
|
||||
"name": "s2",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 56
|
||||
},
|
||||
"s3": {
|
||||
"name": "s3",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"s4": {
|
||||
"name": "s4",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i1": {
|
||||
"name": "i1",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i2": {
|
||||
"name": "i2",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i3": {
|
||||
"name": "i3",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i4": {
|
||||
"name": "i4",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"i5": {
|
||||
"name": "i5",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"h1": {
|
||||
"name": "h1",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"h2": {
|
||||
"name": "h2",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"h3": {
|
||||
"name": "h3",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"h4": {
|
||||
"name": "h4",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"interfaces_table_port_unique": {
|
||||
"name": "interfaces_table_port_unique",
|
||||
"columns": [
|
||||
"port"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"one_time_links_table": {
|
||||
"name": "one_time_links_table",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"one_time_link": {
|
||||
"name": "one_time_link",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"one_time_links_table_one_time_link_unique": {
|
||||
"name": "one_time_links_table_one_time_link_unique",
|
||||
"columns": [
|
||||
"one_time_link"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"one_time_links_table_id_clients_table_id_fk": {
|
||||
"name": "one_time_links_table_id_clients_table_id_fk",
|
||||
"tableFrom": "one_time_links_table",
|
||||
"tableTo": "clients_table",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users_table": {
|
||||
"name": "users_table",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"totp_key": {
|
||||
"name": "totp_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"totp_verified": {
|
||||
"name": "totp_verified",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_table_username_unique": {
|
||||
"name": "users_table_username_unique",
|
||||
"columns": [
|
||||
"username"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"user_configs_table": {
|
||||
"name": "user_configs_table",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"default_mtu": {
|
||||
"name": "default_mtu",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"default_persistent_keepalive": {
|
||||
"name": "default_persistent_keepalive",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"default_dns": {
|
||||
"name": "default_dns",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"default_allowed_ips": {
|
||||
"name": "default_allowed_ips",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"default_j_c": {
|
||||
"name": "default_j_c",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 7
|
||||
},
|
||||
"default_j_min": {
|
||||
"name": "default_j_min",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 10
|
||||
},
|
||||
"default_j_max": {
|
||||
"name": "default_j_max",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 1000
|
||||
},
|
||||
"default_i1": {
|
||||
"name": "default_i1",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"default_i2": {
|
||||
"name": "default_i2",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"default_i3": {
|
||||
"name": "default_i3",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"default_i4": {
|
||||
"name": "default_i4",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"default_i5": {
|
||||
"name": "default_i5",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"host": {
|
||||
"name": "host",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"port": {
|
||||
"name": "port",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(CURRENT_TIMESTAMP)"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_configs_table_id_interfaces_table_name_fk": {
|
||||
"name": "user_configs_table_id_interfaces_table_name_fk",
|
||||
"tableFrom": "user_configs_table",
|
||||
"tableTo": "interfaces_table",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"name"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,13 @@
|
||||
"when": 1748427001203,
|
||||
"tag": "0001_classy_the_stranger",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "6",
|
||||
"when": 1761298328460,
|
||||
"tag": "0002_keen_sleepwalker",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -36,6 +36,14 @@ export const client = sqliteTable('clients_table', {
|
||||
.notNull(),
|
||||
persistentKeepalive: int('persistent_keepalive').notNull(),
|
||||
mtu: int().notNull(),
|
||||
jC: int('j_c'),
|
||||
jMin: int('j_min'),
|
||||
jMax: int('j_max'),
|
||||
i1: text(),
|
||||
i2: text(),
|
||||
i3: text(),
|
||||
i4: text(),
|
||||
i5: text(),
|
||||
dns: text({ mode: 'json' }).$type<string[]>(),
|
||||
serverEndpoint: text('server_endpoint'),
|
||||
enabled: int({ mode: 'boolean' }).notNull(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
import { eq, sql, or, like, and } from 'drizzle-orm';
|
||||
import { containsCidr, parseCidr } from 'cidr-tools';
|
||||
import { client } from './schema';
|
||||
import type {
|
||||
@@ -18,6 +18,17 @@ function createPreparedStatement(db: DBType) {
|
||||
},
|
||||
})
|
||||
.prepare(),
|
||||
findAllPublic: db.query.client
|
||||
.findMany({
|
||||
with: {
|
||||
oneTimeLink: true,
|
||||
},
|
||||
columns: {
|
||||
privateKey: false,
|
||||
preSharedKey: false,
|
||||
},
|
||||
})
|
||||
.prepare(),
|
||||
findById: db.query.client
|
||||
.findFirst({ where: eq(client.id, sql.placeholder('id')) })
|
||||
.prepare(),
|
||||
@@ -25,6 +36,43 @@ function createPreparedStatement(db: DBType) {
|
||||
.findMany({
|
||||
where: eq(client.userId, sql.placeholder('userId')),
|
||||
with: { oneTimeLink: true },
|
||||
columns: {
|
||||
privateKey: false,
|
||||
preSharedKey: false,
|
||||
},
|
||||
})
|
||||
.prepare(),
|
||||
findAllPublicFiltered: db.query.client
|
||||
.findMany({
|
||||
where: or(
|
||||
like(client.name, sql.placeholder('filter')),
|
||||
like(client.ipv4Address, sql.placeholder('filter')),
|
||||
like(client.ipv6Address, sql.placeholder('filter'))
|
||||
),
|
||||
with: {
|
||||
oneTimeLink: true,
|
||||
},
|
||||
columns: {
|
||||
privateKey: false,
|
||||
preSharedKey: false,
|
||||
},
|
||||
})
|
||||
.prepare(),
|
||||
findByUserIdFiltered: db.query.client
|
||||
.findMany({
|
||||
where: and(
|
||||
eq(client.userId, sql.placeholder('userId')),
|
||||
or(
|
||||
like(client.name, sql.placeholder('filter')),
|
||||
like(client.ipv4Address, sql.placeholder('filter')),
|
||||
like(client.ipv6Address, sql.placeholder('filter'))
|
||||
)
|
||||
),
|
||||
with: { oneTimeLink: true },
|
||||
columns: {
|
||||
privateKey: false,
|
||||
preSharedKey: false,
|
||||
},
|
||||
})
|
||||
.prepare(),
|
||||
toggle: db
|
||||
@@ -57,6 +105,9 @@ export class ClientService {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Never return values directly from this function. Use {@link getAllPublic} instead.
|
||||
*/
|
||||
async getAll() {
|
||||
const result = await this.#statements.findAll.execute();
|
||||
return result.map((row) => ({
|
||||
@@ -66,6 +117,53 @@ export class ClientService {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all clients without sensitive data
|
||||
*/
|
||||
async getAllPublic() {
|
||||
const result = await this.#statements.findAllPublic.execute();
|
||||
return result.map((row) => ({
|
||||
...row,
|
||||
createdAt: new Date(row.createdAt),
|
||||
updatedAt: new Date(row.updatedAt),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clients based on user ID and filter conditions
|
||||
*/
|
||||
async getForUserFiltered(userId: ID, filter: string) {
|
||||
const filterPattern = `%${filter.toLowerCase()}%`;
|
||||
|
||||
const result = await this.#statements.findByUserIdFiltered.execute({
|
||||
userId,
|
||||
filter: filterPattern,
|
||||
});
|
||||
|
||||
return result.map((row) => ({
|
||||
...row,
|
||||
createdAt: new Date(row.createdAt),
|
||||
updatedAt: new Date(row.updatedAt),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all clients based on filter conditions without sensitive data
|
||||
*/
|
||||
async getAllPublicFiltered(filter: string) {
|
||||
const filterPattern = `%${filter.toLowerCase()}%`;
|
||||
|
||||
const result = await this.#statements.findAllPublicFiltered.execute({
|
||||
filter: filterPattern,
|
||||
});
|
||||
|
||||
return result.map((row) => ({
|
||||
...row,
|
||||
createdAt: new Date(row.createdAt),
|
||||
updatedAt: new Date(row.updatedAt),
|
||||
}));
|
||||
}
|
||||
|
||||
get(id: ID) {
|
||||
return this.#statements.findById.execute({ id });
|
||||
}
|
||||
@@ -77,32 +175,36 @@ 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 clientConfig = await tx.query.userConfig
|
||||
const clientInterface = applyInterfaceOverrides(_clientInterface);
|
||||
|
||||
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);
|
||||
const ipv6Address = nextIP(6, ipv6Cidr, clients);
|
||||
|
||||
await tx
|
||||
return await tx
|
||||
.insert(client)
|
||||
.values({
|
||||
name,
|
||||
@@ -116,10 +218,19 @@ export class ClientService {
|
||||
ipv4Address,
|
||||
ipv6Address,
|
||||
mtu: clientConfig.defaultMtu,
|
||||
jC: clientConfig.defaultJC,
|
||||
jMin: clientConfig.defaultJMin,
|
||||
jMax: clientConfig.defaultJMax,
|
||||
i1: clientConfig.defaultI1,
|
||||
i2: clientConfig.defaultI2,
|
||||
i3: clientConfig.defaultI3,
|
||||
i4: clientConfig.defaultI4,
|
||||
i5: clientConfig.defaultI5,
|
||||
persistentKeepalive: clientConfig.defaultPersistentKeepalive,
|
||||
serverAllowedIps: [],
|
||||
enabled: true,
|
||||
})
|
||||
.returning({ clientId: client.id })
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
@@ -134,16 +245,18 @@ 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');
|
||||
}
|
||||
@@ -165,7 +278,8 @@ export class ClientService {
|
||||
privateKey,
|
||||
publicKey,
|
||||
}: ClientCreateFromExistingType) {
|
||||
const clientConfig = await Database.userConfigs.get();
|
||||
const _clientConfig = await Database.userConfigs.get();
|
||||
const clientConfig = applyUserConfigOverrides(_clientConfig);
|
||||
|
||||
return this.#db
|
||||
.insert(client)
|
||||
@@ -179,6 +293,13 @@ export class ClientService {
|
||||
ipv4Address,
|
||||
ipv6Address,
|
||||
mtu: clientConfig.defaultMtu,
|
||||
jC: clientConfig.defaultJC,
|
||||
jMin: clientConfig.defaultJMin,
|
||||
jMax: clientConfig.defaultJMax,
|
||||
i1: clientConfig.defaultI1,
|
||||
i2: clientConfig.defaultI2,
|
||||
i3: clientConfig.defaultI3,
|
||||
i4: clientConfig.defaultI4,
|
||||
allowedIps: clientConfig.defaultAllowedIps,
|
||||
dns: clientConfig.defaultDns,
|
||||
persistentKeepalive: clientConfig.defaultPersistentKeepalive,
|
||||
|
||||
@@ -39,6 +39,8 @@ const address6 = z
|
||||
.min(1, { message: t('zod.client.address6') })
|
||||
.pipe(safeStringRefine);
|
||||
|
||||
const filter = z.string().optional();
|
||||
|
||||
const serverAllowedIps = z.array(AddressSchema, {
|
||||
message: t('zod.client.serverAllowedIps'),
|
||||
});
|
||||
@@ -50,6 +52,12 @@ export const ClientCreateSchema = z.object({
|
||||
|
||||
export type ClientCreateType = z.infer<typeof ClientCreateSchema>;
|
||||
|
||||
export const ClientQuerySchema = z.object({
|
||||
filter: filter,
|
||||
});
|
||||
|
||||
export type ClientQueryType = z.infer<typeof ClientQuerySchema>;
|
||||
|
||||
export const ClientUpdateSchema = schemaForType<UpdateClientType>()(
|
||||
z.object({
|
||||
name: name,
|
||||
@@ -64,6 +72,14 @@ export const ClientUpdateSchema = schemaForType<UpdateClientType>()(
|
||||
allowedIps: AllowedIpsSchema.nullable(),
|
||||
serverAllowedIps: serverAllowedIps,
|
||||
mtu: MtuSchema,
|
||||
jC: JcSchema,
|
||||
jMin: JminSchema,
|
||||
jMax: JmaxSchema,
|
||||
i1: ISchema,
|
||||
i2: ISchema,
|
||||
i3: ISchema,
|
||||
i4: ISchema,
|
||||
i5: ISchema,
|
||||
persistentKeepalive: PersistentKeepaliveSchema,
|
||||
serverEndpoint: AddressSchema.nullable(),
|
||||
dns: DnsSchema.nullable(),
|
||||
@@ -71,7 +87,7 @@ export const ClientUpdateSchema = schemaForType<UpdateClientType>()(
|
||||
);
|
||||
|
||||
// TODO: investigate if coerce is bad
|
||||
const clientId = z.number({ message: t('zod.client.id'), coerce: true });
|
||||
const clientId = z.coerce.number({ message: t('zod.client.id') });
|
||||
|
||||
export const ClientGetSchema = z.object({
|
||||
clientId: clientId,
|
||||
|
||||
@@ -13,6 +13,22 @@ export const wgInterface = sqliteTable('interfaces_table', {
|
||||
ipv4Cidr: text('ipv4_cidr').notNull(),
|
||||
ipv6Cidr: text('ipv6_cidr').notNull(),
|
||||
mtu: int().notNull(),
|
||||
jC: int('j_c').default(7),
|
||||
jMin: int('j_min').default(10),
|
||||
jMax: int('j_max').default(1000),
|
||||
s1: int().default(128),
|
||||
s2: int().default(56),
|
||||
s3: int(),
|
||||
s4: int(),
|
||||
i1: text(),
|
||||
i2: text(),
|
||||
i3: text(),
|
||||
i4: text(),
|
||||
i5: text(),
|
||||
h1: int().default(0),
|
||||
h2: int().default(0),
|
||||
h3: int().default(0),
|
||||
h4: int().default(0),
|
||||
// does nothing yet
|
||||
enabled: int({ mode: 'boolean' }).notNull(),
|
||||
createdAt: text('created_at')
|
||||
|
||||
@@ -31,6 +31,22 @@ export const InterfaceUpdateSchema = schemaForType<InterfaceUpdateType>()(
|
||||
ipv4Cidr: cidr,
|
||||
ipv6Cidr: cidr,
|
||||
mtu: MtuSchema,
|
||||
jC: JcSchema,
|
||||
jMin: JminSchema,
|
||||
jMax: JmaxSchema,
|
||||
s1: SSchema,
|
||||
s2: SSchema,
|
||||
s3: SSchema,
|
||||
s4: SSchema,
|
||||
i1: ISchema,
|
||||
i2: ISchema,
|
||||
i3: ISchema,
|
||||
i4: ISchema,
|
||||
i5: ISchema,
|
||||
h1: HSchema,
|
||||
h2: HSchema,
|
||||
h3: HSchema,
|
||||
h4: HSchema,
|
||||
port: PortSchema,
|
||||
device: device,
|
||||
enabled: EnabledSchema,
|
||||
|
||||
@@ -18,6 +18,14 @@ export const userConfig = sqliteTable('user_configs_table', {
|
||||
defaultAllowedIps: text('default_allowed_ips', { mode: 'json' })
|
||||
.$type<string[]>()
|
||||
.notNull(),
|
||||
defaultJC: int('default_j_c').default(7),
|
||||
defaultJMin: int('default_j_min').default(10),
|
||||
defaultJMax: int('default_j_max').default(1000),
|
||||
defaultI1: text('default_i1'),
|
||||
defaultI2: text('default_i2'),
|
||||
defaultI3: text('default_i3'),
|
||||
defaultI4: text('default_i4'),
|
||||
defaultI5: text('default_i5'),
|
||||
host: text().notNull(),
|
||||
port: int().notNull(),
|
||||
createdAt: text('created_at')
|
||||
|
||||
@@ -54,7 +54,7 @@ export class UserConfigService {
|
||||
});
|
||||
}
|
||||
|
||||
update(data: UserConfigUpdateType) {
|
||||
update(data: Partial<UserConfigUpdateType>) {
|
||||
return this.#db
|
||||
.update(userConfig)
|
||||
.set(data)
|
||||
|
||||
@@ -26,6 +26,14 @@ export const UserConfigUpdateSchema = schemaForType<UserConfigUpdateType>()(
|
||||
defaultPersistentKeepalive: PersistentKeepaliveSchema,
|
||||
defaultDns: DnsSchema,
|
||||
defaultAllowedIps: AllowedIpsSchema,
|
||||
defaultJC: JcSchema,
|
||||
defaultJMin: JminSchema,
|
||||
defaultJMax: JmaxSchema,
|
||||
defaultI1: ISchema,
|
||||
defaultI2: ISchema,
|
||||
defaultI3: ISchema,
|
||||
defaultI4: ISchema,
|
||||
defaultI5: ISchema,
|
||||
host: host,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -89,30 +89,39 @@ async function initialSetup(db: DBServiceType) {
|
||||
|
||||
if (WG_INITIAL_ENV.DNS) {
|
||||
DB_DEBUG('Setting initial DNS...');
|
||||
const userConfig = await db.userConfigs.get();
|
||||
await db.userConfigs.update({
|
||||
...userConfig,
|
||||
defaultDns: WG_INITIAL_ENV.DNS,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
WG_INITIAL_ENV.USERNAME &&
|
||||
WG_INITIAL_ENV.PASSWORD &&
|
||||
WG_INITIAL_ENV.HOST &&
|
||||
WG_INITIAL_ENV.PORT
|
||||
) {
|
||||
if (WG_INITIAL_ENV.ALLOWED_IPS) {
|
||||
DB_DEBUG('Setting initial Allowed IPs...');
|
||||
await db.userConfigs.update({
|
||||
defaultAllowedIps: WG_INITIAL_ENV.ALLOWED_IPS,
|
||||
});
|
||||
}
|
||||
|
||||
if (WG_INITIAL_ENV.USERNAME && WG_INITIAL_ENV.PASSWORD) {
|
||||
DB_DEBUG('Creating initial user...');
|
||||
await db.users.create(WG_INITIAL_ENV.USERNAME, WG_INITIAL_ENV.PASSWORD);
|
||||
|
||||
DB_DEBUG('Setting initial host and port...');
|
||||
await db.userConfigs.updateHostPort(
|
||||
WG_INITIAL_ENV.HOST,
|
||||
WG_INITIAL_ENV.PORT
|
||||
);
|
||||
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);
|
||||
|
||||
// Setup completion requires USERNAME and PASSWORD (no overrides for these)
|
||||
if (WG_INITIAL_ENV.USERNAME && WG_INITIAL_ENV.PASSWORD) {
|
||||
await db.general.setSetupStep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function disableIpv6(db: DBType) {
|
||||
|
||||
@@ -9,12 +9,17 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
const { step, done } = await Database.general.getSetupStep();
|
||||
if (!done) {
|
||||
const parsedSetup = url.pathname.match(/\/setup\/(\d)/);
|
||||
const parsedSetup = url.pathname.match(/\/setup\/(\d|migrate|success)/);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ export default defineEventHandler(async (event) => {
|
||||
setHeader(
|
||||
event,
|
||||
'Content-Disposition',
|
||||
`attachment; filename="${client.name}.conf"`
|
||||
`attachment; filename="${WireGuard.cleanClientFilename(client.name) || client.id}.conf"`
|
||||
);
|
||||
setHeader(event, 'Content-Type', 'text/plain');
|
||||
setHeader(event, 'Content-Type', 'application/octet-stream');
|
||||
return config;
|
||||
});
|
||||
|
||||
@@ -5,12 +5,18 @@ import type { InterfaceType } from '#db/repositories/interface/types';
|
||||
|
||||
const WG_DEBUG = debug('WireGuard');
|
||||
|
||||
const generateRandomHeaderValue = () =>
|
||||
Math.floor(Math.random() * 2147483642) + 5;
|
||||
|
||||
class WireGuard {
|
||||
/**
|
||||
* Save and sync config
|
||||
*/
|
||||
async saveConfig() {
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
const wgInterface = applyInterfaceOverrides(
|
||||
await Database.interfaces.get()
|
||||
);
|
||||
|
||||
await this.#saveWireguardConfig(wgInterface);
|
||||
await this.#syncWireguardConfig(wgInterface);
|
||||
}
|
||||
@@ -22,7 +28,7 @@ class WireGuard {
|
||||
*/
|
||||
async #saveWireguardConfig(wgInterface: InterfaceType) {
|
||||
const clients = await Database.clients.getAll();
|
||||
const hooks = await Database.hooks.get();
|
||||
const hooks = applyHooksOverrides(await Database.hooks.get());
|
||||
|
||||
const result = [];
|
||||
result.push(
|
||||
@@ -61,10 +67,15 @@ class WireGuard {
|
||||
WG_DEBUG('Config synced successfully.');
|
||||
}
|
||||
|
||||
async getClientsForUser(userId: ID) {
|
||||
async getClientsForUser(userId: ID, filter?: string) {
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
|
||||
const dbClients = await Database.clients.getForUser(userId);
|
||||
let dbClients;
|
||||
if (filter?.trim()) {
|
||||
dbClients = await Database.clients.getForUserFiltered(userId, filter);
|
||||
} else {
|
||||
dbClients = await Database.clients.getForUser(userId);
|
||||
}
|
||||
|
||||
const clients = dbClients.map((client) => ({
|
||||
...client,
|
||||
@@ -93,9 +104,27 @@ class WireGuard {
|
||||
return clients;
|
||||
}
|
||||
|
||||
async getAllClients() {
|
||||
async dumpByPublicKey(publicKey: string) {
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
const dbClients = await Database.clients.getAll();
|
||||
|
||||
const dump = await wg.dump(wgInterface.name);
|
||||
const clientDump = dump.find(
|
||||
({ publicKey: dumpPublicKey }) => dumpPublicKey === publicKey
|
||||
);
|
||||
|
||||
return clientDump;
|
||||
}
|
||||
|
||||
async getAllClients(filter?: string) {
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
|
||||
let dbClients;
|
||||
if (filter?.trim()) {
|
||||
dbClients = await Database.clients.getAllPublicFiltered(filter);
|
||||
} else {
|
||||
dbClients = await Database.clients.getAllPublic();
|
||||
}
|
||||
|
||||
const clients = dbClients.map((client) => ({
|
||||
...client,
|
||||
latestHandshakeAt: null as Date | null,
|
||||
@@ -124,8 +153,12 @@ class WireGuard {
|
||||
}
|
||||
|
||||
async getClientConfiguration({ clientId }: { clientId: ID }) {
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
const userConfig = await Database.userConfigs.get();
|
||||
const wgInterface = applyInterfaceOverrides(
|
||||
await Database.interfaces.get()
|
||||
);
|
||||
const userConfig = applyUserConfigOverrides(
|
||||
await Database.userConfigs.get()
|
||||
);
|
||||
|
||||
const client = await Database.clients.get(clientId);
|
||||
|
||||
@@ -147,6 +180,14 @@ class WireGuard {
|
||||
});
|
||||
}
|
||||
|
||||
cleanClientFilename(name: string): string {
|
||||
return name
|
||||
.replace(/[^a-zA-Z0-9_=+.-]/g, '-')
|
||||
.replace(/(-{2,}|-$)/g, '-')
|
||||
.replace(/-$/, '')
|
||||
.substring(0, 32);
|
||||
}
|
||||
|
||||
async Startup() {
|
||||
WG_DEBUG('Starting WireGuard...');
|
||||
// let as it has to refetch if keys change
|
||||
@@ -165,25 +206,51 @@ class WireGuard {
|
||||
wgInterface = await Database.interfaces.get();
|
||||
WG_DEBUG('New Wireguard Keys generated successfully.');
|
||||
}
|
||||
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 (WG_ENV.WG_EXECUTABLE === 'awg' && wgInterface.h1 === 0) {
|
||||
WG_DEBUG('Generating random AmneziaWG obfuscation parameters...');
|
||||
const headers = new Set<number>();
|
||||
|
||||
while (headers.size < 4) {
|
||||
headers.add(generateRandomHeaderValue());
|
||||
}
|
||||
const [h1, h2, h3, h4] = Array.from(headers);
|
||||
|
||||
wgInterface.h1 = h1!;
|
||||
wgInterface.h2 = h2!;
|
||||
wgInterface.h3 = h3!;
|
||||
wgInterface.h4 = h4!;
|
||||
|
||||
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) => {
|
||||
if (
|
||||
err &&
|
||||
err.message &&
|
||||
err.message.includes(`Cannot find device "${wgInterface.name}"`)
|
||||
err.message.includes(
|
||||
`Cannot find device "${wgInterfaceWithOverrides.name}"`
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`WireGuard exited with the error: Cannot find device "${wgInterface.name}"\nThis usually means that your host's kernel does not support WireGuard!`,
|
||||
`WireGuard exited with the error: Cannot find device "${wgInterfaceWithOverrides.name}"\nThis usually means that your host's kernel does not support WireGuard!`,
|
||||
{ cause: err.message }
|
||||
);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
await this.#syncWireguardConfig(wgInterface);
|
||||
WG_DEBUG(`Wireguard Interface ${wgInterface.name} started successfully.`);
|
||||
await this.#syncWireguardConfig(wgInterfaceWithOverrides);
|
||||
WG_DEBUG(
|
||||
`Wireguard Interface ${wgInterfaceWithOverrides.name} started successfully.`
|
||||
);
|
||||
|
||||
WG_DEBUG('Starting Cron Job...');
|
||||
await this.startCronJob();
|
||||
@@ -202,12 +269,16 @@ class WireGuard {
|
||||
|
||||
// Shutdown wireguard
|
||||
async Shutdown() {
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
const wgInterface = applyInterfaceOverrides(
|
||||
await Database.interfaces.get()
|
||||
);
|
||||
await wg.down(wgInterface.name).catch(() => {});
|
||||
}
|
||||
|
||||
async Restart() {
|
||||
const wgInterface = await Database.interfaces.get();
|
||||
const wgInterface = applyInterfaceOverrides(
|
||||
await Database.interfaces.get()
|
||||
);
|
||||
await wg.restart(wgInterface.name);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,24 @@ export const OLD_ENV = {
|
||||
PASSWORD_HASH: process.env.PASSWORD_HASH,
|
||||
};
|
||||
|
||||
const detectAwg = async (): Promise<'awg' | 'wg'> => {
|
||||
/** TODO: delete on next major version */
|
||||
if (process.env.EXPERIMENTAL_AWG === 'true') {
|
||||
const OVERRIDE_AUTO_AWG = process.env.OVERRIDE_AUTO_AWG?.toLowerCase();
|
||||
|
||||
if (
|
||||
OVERRIDE_AUTO_AWG === ('wg' as const) ||
|
||||
OVERRIDE_AUTO_AWG === ('awg' as const)
|
||||
) {
|
||||
return OVERRIDE_AUTO_AWG;
|
||||
} else {
|
||||
return await exec('modinfo amneziawg')
|
||||
.then(() => 'awg' as const)
|
||||
.catch(() => 'wg' as const);
|
||||
}
|
||||
} else return 'wg';
|
||||
};
|
||||
|
||||
export const WG_ENV = {
|
||||
/** UI is hosted on HTTP instead of HTTPS */
|
||||
INSECURE: process.env.INSECURE === 'true',
|
||||
@@ -19,6 +37,7 @@ export const WG_ENV = {
|
||||
PORT: assertEnv('PORT'),
|
||||
/** If IPv6 should be disabled */
|
||||
DISABLE_IPV6: process.env.DISABLE_IPV6 === 'true',
|
||||
WG_EXECUTABLE: await detectAwg(),
|
||||
};
|
||||
|
||||
export const WG_INITIAL_ENV = {
|
||||
@@ -28,12 +47,85 @@ export const WG_INITIAL_ENV = {
|
||||
DNS: process.env.INIT_DNS?.split(',').map((x) => x.trim()),
|
||||
IPV4_CIDR: process.env.INIT_IPV4_CIDR,
|
||||
IPV6_CIDR: process.env.INIT_IPV6_CIDR,
|
||||
ALLOWED_IPS: process.env.INIT_ALLOWED_IPS?.split(',').map((x) => x.trim()),
|
||||
HOST: process.env.INIT_HOST,
|
||||
PORT: process.env.INIT_PORT
|
||||
? Number.parseInt(process.env.INIT_PORT, 10)
|
||||
: 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];
|
||||
|
||||
@@ -43,3 +135,105 @@ 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,7 +138,9 @@ export const defineMetricsHandler = <
|
||||
handler: MetricsHandler<TReq, TRes>
|
||||
) => {
|
||||
return defineEventHandler(async (event) => {
|
||||
const metricsConfig = await Database.general.getMetricsConfig();
|
||||
const metricsConfig = applyMetricsOverrides(
|
||||
await Database.general.getMetricsConfig()
|
||||
);
|
||||
|
||||
if (metricsConfig.password) {
|
||||
const auth = getHeader(event, 'Authorization');
|
||||
|
||||
@@ -8,7 +8,10 @@ export type WGSession = Partial<{
|
||||
const name = 'wg-easy';
|
||||
|
||||
export async function useWGSession(event: H3Event, rememberMe = false) {
|
||||
const sessionConfig = await Database.general.getSessionConfig();
|
||||
const sessionConfig = applySessionOverrides(
|
||||
await Database.general.getSessionConfig()
|
||||
);
|
||||
|
||||
return useSession<WGSession>(event, {
|
||||
password: sessionConfig.sessionPassword,
|
||||
name,
|
||||
@@ -22,7 +25,10 @@ export async function useWGSession(event: H3Event, rememberMe = false) {
|
||||
}
|
||||
|
||||
export async function getWGSession(event: H3Event) {
|
||||
const sessionConfig = await Database.general.getSessionConfig();
|
||||
const sessionConfig = applySessionOverrides(
|
||||
await Database.general.getSessionConfig()
|
||||
);
|
||||
|
||||
return getSession<WGSession>(event, {
|
||||
password: sessionConfig.sessionPassword,
|
||||
name,
|
||||
|
||||
@@ -22,9 +22,22 @@ export const EnabledSchema = z.boolean({ message: t('zod.enabled') });
|
||||
|
||||
export const MtuSchema = z
|
||||
.number({ message: t('zod.mtu') })
|
||||
.min(1280, { message: t('zod.mtu') })
|
||||
// min for IPv6 is 1280, but we allow lower for IPv4
|
||||
.min(1024, { message: t('zod.mtu') })
|
||||
.max(9000, { message: t('zod.mtu') });
|
||||
|
||||
export const JcSchema = z.number().min(1).max(128).nullable();
|
||||
|
||||
export const JminSchema = z.number().max(1279).nullable();
|
||||
|
||||
export const JmaxSchema = z.number().max(1280).nullable();
|
||||
|
||||
export const SSchema = z.number().max(1132).nullable();
|
||||
|
||||
export const HSchema = z.number().min(5).max(2147483647).nullable();
|
||||
|
||||
export const ISchema = z.string().nullable();
|
||||
|
||||
export const PortSchema = z
|
||||
.number({ message: t('zod.port') })
|
||||
.min(1, { message: t('zod.port') })
|
||||
@@ -40,9 +53,7 @@ export const AddressSchema = z
|
||||
.min(1, { message: t('zod.address') })
|
||||
.pipe(safeStringRefine);
|
||||
|
||||
export const DnsSchema = z
|
||||
.array(AddressSchema, { message: t('zod.dns') })
|
||||
.min(1, t('zod.dns'));
|
||||
export const DnsSchema = z.array(AddressSchema, { message: t('zod.dns') });
|
||||
|
||||
export const AllowedIpsSchema = z
|
||||
.array(AddressSchema, { message: t('zod.allowedIps') })
|
||||
@@ -84,7 +95,7 @@ export function validateZod<T>(
|
||||
if (v.message.startsWith('zod.')) {
|
||||
switch (v.code) {
|
||||
case 'too_small':
|
||||
switch (v.type) {
|
||||
switch (v.origin) {
|
||||
case 'string':
|
||||
newMessage = t('zod.generic.stringMin', [
|
||||
t(v.message),
|
||||
@@ -100,7 +111,7 @@ export function validateZod<T>(
|
||||
}
|
||||
break;
|
||||
case 'invalid_type': {
|
||||
if (v.received === 'null' || v.received === 'undefined') {
|
||||
if (v.input === null || v.input === undefined) {
|
||||
newMessage = t('zod.generic.required', [
|
||||
v.path.join('.'),
|
||||
]);
|
||||
|
||||
@@ -9,6 +9,8 @@ type Options = {
|
||||
enableIpv6?: boolean;
|
||||
};
|
||||
|
||||
const wgExecutable = WG_ENV.WG_EXECUTABLE;
|
||||
|
||||
export const wg = {
|
||||
generateServerPeer: (
|
||||
client: Omit<ClientType, 'createdAt' | 'updatedAt'>,
|
||||
@@ -50,6 +52,35 @@ AllowedIPs = ${allowedIps.join(', ')}${extraLines.length ? `\n${extraLines.join(
|
||||
`${ipv4Addr}/${cidr4.prefix}` +
|
||||
(enableIpv6 ? `, ${ipv6Addr}/${cidr6.prefix}` : '');
|
||||
|
||||
let awgLines: string[] = [];
|
||||
|
||||
if (wgExecutable === 'awg') {
|
||||
const parameters = {
|
||||
Jc: wgInterface.jC,
|
||||
Jmin: wgInterface.jMin,
|
||||
Jmax: wgInterface.jMax,
|
||||
S1: wgInterface.s1,
|
||||
S2: wgInterface.s2,
|
||||
S3: wgInterface.s3,
|
||||
S4: wgInterface.s4,
|
||||
i1: wgInterface.i1,
|
||||
i2: wgInterface.i2,
|
||||
i3: wgInterface.i3,
|
||||
i4: wgInterface.i4,
|
||||
i5: wgInterface.i5,
|
||||
H1: wgInterface.h1,
|
||||
H2: wgInterface.h2,
|
||||
H3: wgInterface.h3,
|
||||
H4: wgInterface.h4,
|
||||
} as const;
|
||||
|
||||
awgLines = Object.entries(parameters)
|
||||
.filter(([_, value]) => !!value)
|
||||
.map(([key, value]) => `${key} = ${value}`);
|
||||
}
|
||||
|
||||
const extraLines = [...awgLines].filter((v) => v !== null);
|
||||
|
||||
return `# Note: Do not edit this file directly.
|
||||
# Your changes will be overwritten!
|
||||
|
||||
@@ -59,6 +90,7 @@ PrivateKey = ${wgInterface.privateKey}
|
||||
Address = ${address}
|
||||
ListenPort = ${wgInterface.port}
|
||||
MTU = ${wgInterface.mtu}
|
||||
${extraLines.length ? `${extraLines.join('\n')}\n` : ''}
|
||||
PreUp = ${iptablesTemplate(hooks.preUp, wgInterface)}
|
||||
PostUp = ${iptablesTemplate(hooks.postUp, wgInterface)}
|
||||
PreDown = ${iptablesTemplate(hooks.preDown, wgInterface)}
|
||||
@@ -73,26 +105,57 @@ PostDown = ${iptablesTemplate(hooks.postDown, wgInterface)}`;
|
||||
) => {
|
||||
const { enableIpv6 = true } = options;
|
||||
|
||||
const cidr4Block = parseCidr(wgInterface.ipv4Cidr).prefix;
|
||||
const cidr6Block = parseCidr(wgInterface.ipv6Cidr).prefix;
|
||||
|
||||
const address =
|
||||
`${client.ipv4Address}/${cidr4Block}` +
|
||||
(enableIpv6 ? `, ${client.ipv6Address}/${cidr6Block}` : '');
|
||||
`${client.ipv4Address}/32` +
|
||||
(enableIpv6 ? `, ${client.ipv6Address}/128` : '');
|
||||
|
||||
const hookLines = [
|
||||
client.preUp ? `PreUp = ${client.preUp}` : null,
|
||||
client.postUp ? `PostUp = ${client.postUp}` : null,
|
||||
client.preDown ? `PreDown = ${client.preDown}` : null,
|
||||
client.postDown ? `PostDown = ${client.postDown}` : null,
|
||||
].filter((v) => v !== null);
|
||||
];
|
||||
|
||||
const dnsServers = client.dns ?? userConfig.defaultDns;
|
||||
const dnsLine =
|
||||
dnsServers.length > 0 ? `DNS = ${dnsServers.join(', ')}` : null;
|
||||
|
||||
let awgLines: string[] = [];
|
||||
|
||||
if (wgExecutable === 'awg') {
|
||||
const parameters = {
|
||||
Jc: client.jC,
|
||||
Jmin: client.jMin,
|
||||
Jmax: client.jMax,
|
||||
S1: wgInterface.s1,
|
||||
S2: wgInterface.s2,
|
||||
S3: wgInterface.s3,
|
||||
S4: wgInterface.s4,
|
||||
i1: client.i1,
|
||||
i2: client.i2,
|
||||
i3: client.i3,
|
||||
i4: client.i4,
|
||||
i5: client.i5,
|
||||
H1: wgInterface.h1,
|
||||
H2: wgInterface.h2,
|
||||
H3: wgInterface.h3,
|
||||
H4: wgInterface.h4,
|
||||
} as const;
|
||||
|
||||
awgLines = Object.entries(parameters)
|
||||
.filter(([_, value]) => !!value)
|
||||
.map(([key, value]) => `${key} = ${value}`);
|
||||
}
|
||||
|
||||
const extraLines = [dnsLine, ...hookLines, ...awgLines].filter(
|
||||
(v) => v !== null
|
||||
);
|
||||
|
||||
return `[Interface]
|
||||
PrivateKey = ${client.privateKey}
|
||||
Address = ${address}
|
||||
DNS = ${(client.dns ?? userConfig.defaultDns).join(', ')}
|
||||
MTU = ${client.mtu}
|
||||
${hookLines.length ? `${hookLines.join('\n')}\n` : ''}
|
||||
${extraLines.length ? `${extraLines.join('\n')}\n` : ''}
|
||||
[Peer]
|
||||
PublicKey = ${wgInterface.publicKey}
|
||||
PresharedKey = ${client.preSharedKey}
|
||||
@@ -102,37 +165,41 @@ Endpoint = ${userConfig.host}:${userConfig.port}`;
|
||||
},
|
||||
|
||||
generatePrivateKey: () => {
|
||||
return exec('wg genkey');
|
||||
return exec(`${wgExecutable} genkey`);
|
||||
},
|
||||
|
||||
getPublicKey: (privateKey: string) => {
|
||||
return exec(`echo ${privateKey} | wg pubkey`, {
|
||||
log: 'echo ***hidden*** | wg pubkey',
|
||||
return exec(`echo ${privateKey} | ${wgExecutable} pubkey`, {
|
||||
log: `echo ***hidden*** | ${wgExecutable} pubkey`,
|
||||
});
|
||||
},
|
||||
|
||||
generatePreSharedKey: () => {
|
||||
return exec('wg genpsk');
|
||||
return exec(`${wgExecutable} genpsk`);
|
||||
},
|
||||
|
||||
up: (infName: string) => {
|
||||
return exec(`wg-quick up ${infName}`);
|
||||
return exec(`${wgExecutable}-quick up ${infName}`);
|
||||
},
|
||||
|
||||
down: (infName: string) => {
|
||||
return exec(`wg-quick down ${infName}`);
|
||||
return exec(`${wgExecutable}-quick down ${infName}`);
|
||||
},
|
||||
|
||||
restart: (infName: string) => {
|
||||
return exec(`wg-quick down ${infName}; wg-quick up ${infName}`);
|
||||
return exec(
|
||||
`${wgExecutable}-quick down ${infName}; ${wgExecutable}-quick up ${infName}`
|
||||
);
|
||||
},
|
||||
|
||||
sync: (infName: string) => {
|
||||
return exec(`wg syncconf ${infName} <(wg-quick strip ${infName})`);
|
||||
return exec(
|
||||
`${wgExecutable} syncconf ${infName} <(${wgExecutable}-quick strip ${infName})`
|
||||
);
|
||||
},
|
||||
|
||||
dump: async (infName: string) => {
|
||||
const rawDump = await exec(`wg show ${infName} dump`, {
|
||||
const rawDump = await exec(`${wgExecutable} show ${infName} dump`, {
|
||||
log: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ type RolesWithPermissions = {
|
||||
|
||||
export type Permissions = {
|
||||
clients: {
|
||||
dataType: ClientType;
|
||||
dataType: Pick<ClientType, 'id' | 'userId'>;
|
||||
action: 'view' | 'create' | 'update' | 'delete' | 'custom';
|
||||
};
|
||||
admin: {
|
||||
|
||||
Reference in New Issue
Block a user