Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9368b857e8 | |||
| 0f663df7f6 | |||
| 68fde7d165 | |||
| 501a784264 | |||
| 629c184195 | |||
| 76b8818a33 | |||
| 6c52301a64 | |||
| be26db63ca | |||
| 962bfa213f | |||
| ee00e5c914 | |||
| 6343213538 | |||
| 187bdc0836 | |||
| f2520f0481 | |||
| 5e9a73645b | |||
| 783fa3286c | |||
| 77b4f9db65 | |||
| 0f6f07161b | |||
| d75a836de9 | |||
| f79b0fd025 | |||
| d2ce82241b | |||
| 8c395ec275 | |||
| 10f42170f3 | |||
| a8aa85bdaa | |||
| 7e1aa5807d | |||
| df57921b8e | |||
| 4fbf059e61 | |||
| b150e3f3b4 | |||
| aed10ab0bd | |||
| 478e7207b2 | |||
| c8dc710435 | |||
| 19d9e3b7d7 | |||
| c4efb1d03a | |||
| 529b9eeb88 | |||
| 69ee741d7e | |||
| f2dc38e91b | |||
| 64e9484331 | |||
| c777fa30b3 | |||
| 734d91fd98 | |||
| 84ed7b299f | |||
| 1cfe6404b2 | |||
| 2a32c1b9c0 |
@@ -1,23 +0,0 @@
|
|||||||
# http://editorconfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
# The JSON files contain newlines inconsistently
|
|
||||||
[*.json]
|
|
||||||
insert_final_newline = ignore
|
|
||||||
|
|
||||||
# Minified JavaScript files shouldn't be changed
|
|
||||||
[**.min.js]
|
|
||||||
indent_style = ignore
|
|
||||||
insert_final_newline = ignore
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
@@ -4,21 +4,38 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker-build:
|
||||||
name: Build & Deploy Docker
|
name: Build Docker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.arch.os }}
|
||||||
if: github.repository_owner == 'wg-easy'
|
if: github.repository_owner == 'wg-easy'
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch:
|
||||||
|
- platform: linux/amd64
|
||||||
|
os: ubuntu-latest
|
||||||
|
- platform: linux/arm64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
- platform: linux/arm/v7
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Prepare
|
||||||
uses: docker/setup-qemu-action@v3
|
run: |
|
||||||
|
platform=${{ matrix.arch.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Docker meta
|
||||||
uses: docker/setup-buildx-action@v3
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -27,15 +44,83 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build & Publish Docker Image
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
platforms: ${{ matrix.arch.platform }}
|
||||||
platforms: linux/amd64,linux/arm64
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
tags: ghcr.io/wg-easy/wg-easy:development
|
tags: ghcr.io/wg-easy/wg-easy
|
||||||
cache-from: type=gha
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
cache-to: type=gha,mode=min
|
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
docker-merge:
|
||||||
|
name: Merge & Deploy Docker
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
needs: docker-build
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.temp }}/digests
|
||||||
|
pattern: digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=raw,value=development
|
||||||
|
|
||||||
|
- name: Create manifest list and push
|
||||||
|
working-directory: ${{ runner.temp }}/digests
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
|
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
name: Build & Deploy Docs
|
name: Build & Deploy Docs
|
||||||
@@ -43,7 +128,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'wg-easy'
|
if: github.repository_owner == 'wg-easy'
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
needs: docker
|
needs: docker-merge
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
name: Edge
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-build:
|
||||||
|
name: Build Docker
|
||||||
|
runs-on: ${{ matrix.arch.os }}
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch:
|
||||||
|
- platform: linux/amd64
|
||||||
|
os: ubuntu-latest
|
||||||
|
- platform: linux/arm64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
- platform: linux/arm/v7
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.arch.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ matrix.arch.platform }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
tags: ghcr.io/wg-easy/wg-easy
|
||||||
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
docker-merge:
|
||||||
|
name: Merge & Deploy Docker
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
needs: docker-build
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.temp }}/digests
|
||||||
|
pattern: digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=raw,value=edge
|
||||||
|
|
||||||
|
- name: Create manifest list and push
|
||||||
|
working-directory: ${{ runner.temp }}/digests
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
|
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
|
||||||
|
|
||||||
|
docs:
|
||||||
|
name: Build & Deploy Docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
needs: docker-merge
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.11.9
|
||||||
|
cache: "pip"
|
||||||
|
cache-dependency-path: docs/requirements.txt
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r docs/requirements.txt
|
||||||
|
|
||||||
|
- name: Setup Git User
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'github-actions[bot]'
|
||||||
|
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
|
||||||
|
- name: Build Docs Website
|
||||||
|
run: |
|
||||||
|
cd docs
|
||||||
|
git fetch origin gh-pages --depth=1 || true
|
||||||
|
|
||||||
|
mike deploy --push --update-aliases edge
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
name: Nightly
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
name: Build & Deploy Docker
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository_owner == 'wg-easy'
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: master
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build & Publish Docker Image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
tags: ghcr.io/wg-easy/wg-easy:nightly
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=min
|
|
||||||
|
|
||||||
docs:
|
|
||||||
name: Build & Deploy Docs
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository_owner == 'wg-easy'
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
needs: docker
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: master
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: 3.11.9
|
|
||||||
cache: "pip"
|
|
||||||
cache-dependency-path: docs/requirements.txt
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
pip install -r docs/requirements.txt
|
|
||||||
|
|
||||||
- name: Setup Git User
|
|
||||||
run: |
|
|
||||||
git config --global user.name 'github-actions[bot]'
|
|
||||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
|
||||||
|
|
||||||
- name: Build Docs Website
|
|
||||||
run: |
|
|
||||||
cd docs
|
|
||||||
git fetch origin gh-pages --depth=1 || true
|
|
||||||
|
|
||||||
mike deploy --push --update-aliases nightly
|
|
||||||
@@ -11,14 +11,26 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
name: Build Docker
|
name: Build Docker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.arch.os }}
|
||||||
if: github.repository_owner == 'wg-easy'
|
if: github.repository_owner == 'wg-easy'
|
||||||
permissions:
|
strategy:
|
||||||
packages: write
|
fail-fast: false
|
||||||
contents: read
|
matrix:
|
||||||
|
arch:
|
||||||
|
- platform: linux/amd64
|
||||||
|
os: ubuntu-latest
|
||||||
|
- platform: linux/arm64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
- platform: linux/arm/v7
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.arch.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
@@ -37,7 +49,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: ${{ matrix.arch.platform }}
|
||||||
tags: ghcr.io/wg-easy/wg-easy:pr
|
tags: ghcr.io/wg-easy/wg-easy:pr
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=min
|
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
|||||||
@@ -10,20 +10,103 @@ on:
|
|||||||
# as this will break the latest and major tags
|
# as this will break the latest and major tags
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker-build:
|
||||||
name: Build & Deploy Docker
|
name: Build Docker
|
||||||
|
runs-on: ${{ matrix.arch.os }}
|
||||||
|
if: |
|
||||||
|
github.repository_owner == 'wg-easy' &&
|
||||||
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch:
|
||||||
|
- platform: linux/amd64
|
||||||
|
os: ubuntu-latest
|
||||||
|
- platform: linux/arm64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
- platform: linux/arm/v7
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.arch.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ matrix.arch.platform }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
tags: ghcr.io/wg-easy/wg-easy
|
||||||
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
docker-merge:
|
||||||
|
name: Merge & Deploy Docker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: |
|
if: |
|
||||||
github.repository_owner == 'wg-easy' &&
|
github.repository_owner == 'wg-easy' &&
|
||||||
startsWith(github.ref, 'refs/tags/v')
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
needs: docker-build
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.temp }}/digests
|
||||||
|
pattern: digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -41,23 +124,15 @@ jobs:
|
|||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Create manifest list and push
|
||||||
uses: docker/login-action@v3
|
working-directory: ${{ runner.temp }}/digests
|
||||||
with:
|
run: |
|
||||||
registry: ghcr.io
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
username: ${{ github.actor }}
|
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build & Publish Docker Image
|
- name: Inspect image
|
||||||
uses: docker/build-push-action@v6
|
run: |
|
||||||
with:
|
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=min
|
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
name: Build & Deploy Docs
|
name: Build & Deploy Docs
|
||||||
@@ -67,7 +142,7 @@ jobs:
|
|||||||
startsWith(github.ref, 'refs/tags/v')
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
needs: docker
|
needs: docker-merge
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,36 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
docs:
|
||||||
|
name: Check Docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "lts/*"
|
||||||
|
check-latest: true
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Check docs formatting
|
||||||
|
run: |
|
||||||
|
pnpm install
|
||||||
|
pnpm format:check:docs
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: docs
|
||||||
if: github.repository_owner == 'wg-easy'
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.swp
|
*.swp
|
||||||
|
node_modules
|
||||||
Vendored
+2
-1
@@ -9,6 +9,7 @@
|
|||||||
"yoavbls.pretty-ts-errors",
|
"yoavbls.pretty-ts-errors",
|
||||||
"bradlc.vscode-tailwindcss",
|
"bradlc.vscode-tailwindcss",
|
||||||
"vue.volar",
|
"vue.volar",
|
||||||
"lokalise.i18n-ally"
|
"lokalise.i18n-ally",
|
||||||
|
"DavidAnson.vscode-markdownlint"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
@@ -18,6 +18,11 @@
|
|||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
|
"[markdown]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"editor.useTabStops": false
|
||||||
|
},
|
||||||
"typescript.tsdk": "./src/node_modules/typescript/lib",
|
"typescript.tsdk": "./src/node_modules/typescript/lib",
|
||||||
"i18n-ally.enabledFrameworks": ["vue"],
|
"i18n-ally.enabledFrameworks": ["vue"],
|
||||||
"i18n-ally.localesPaths": ["src/i18n/locales"],
|
"i18n-ally.localesPaths": ["src/i18n/locales"],
|
||||||
|
|||||||
+26
-2
@@ -5,7 +5,29 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [15.1.0] - 2025-07-01
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Added Ukrainian language (#1906)
|
||||||
|
- Add French language (#1924)
|
||||||
|
- docs for caddy example (#1939)
|
||||||
|
- add docs on how to add/update translation (be26db6)
|
||||||
|
- Add german translations (#1889)
|
||||||
|
- feat: Add Traditional Chinese (zh-HK) i18n Support (#1988)
|
||||||
|
- Add Chinese Simplified (#1990)
|
||||||
|
- Add option to disable ipv6 (#1951)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Updated container launch commands (#1989)
|
||||||
|
- update screenshot (962bfa2)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
|
||||||
|
## [15.0.0] - 2025-05-28
|
||||||
|
|
||||||
We're super excited to announce v15!
|
We're super excited to announce v15!
|
||||||
This update is an entire rewrite to make it even easier to set up your own VPN.
|
This update is an entire rewrite to make it even easier to set up your own VPN.
|
||||||
@@ -27,11 +49,13 @@ As the whole setup has changed, we recommend to start from scratch. And import y
|
|||||||
- SQLite Database
|
- SQLite Database
|
||||||
- Deprecated Dockerless Installations
|
- Deprecated Dockerless Installations
|
||||||
- Added Docker Volume Mount (`/lib/modules`)
|
- Added Docker Volume Mount (`/lib/modules`)
|
||||||
- Removed ARMv6 and ARMv7 support
|
- Removed ARMv6 support
|
||||||
- Connections over HTTP require setting the `INSECURE` env var
|
- Connections over HTTP require setting the `INSECURE` env var
|
||||||
- Changed license from CC BY-NC-SA 4.0 to AGPL-3.0-only
|
- Changed license from CC BY-NC-SA 4.0 to AGPL-3.0-only
|
||||||
- Added 2FA using TOTP
|
- Added 2FA using TOTP
|
||||||
- Improved mobile support
|
- Improved mobile support
|
||||||
|
- CLI
|
||||||
|
- Replaced `nightly` with `edge`
|
||||||
|
|
||||||
## [14.0.0] - 2024-09-04
|
## [14.0.0] - 2024-09-04
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -25,8 +25,13 @@ HEALTHCHECK --interval=1m --timeout=5s --retries=3 CMD /usr/bin/timeout 5s /bin/
|
|||||||
COPY --from=build /app/.output /app
|
COPY --from=build /app/.output /app
|
||||||
# Copy migrations
|
# Copy migrations
|
||||||
COPY --from=build /app/server/database/migrations /app/server/database/migrations
|
COPY --from=build /app/server/database/migrations /app/server/database/migrations
|
||||||
# libsql
|
# libsql (https://github.com/nitrojs/nitro/issues/3328)
|
||||||
RUN cd /app/server && npm install --no-save libsql
|
RUN cd /app/server && \
|
||||||
|
npm install --no-save libsql && \
|
||||||
|
npm cache clean --force
|
||||||
|
# cli
|
||||||
|
COPY --from=build /app/cli/cli.sh /usr/local/bin/cli
|
||||||
|
RUN chmod +x /usr/local/bin/cli
|
||||||
|
|
||||||
# Install Linux packages
|
# Install Linux packages
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
@@ -49,6 +54,7 @@ ENV PORT=51821
|
|||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV INSECURE=false
|
ENV INSECURE=false
|
||||||
ENV INIT_ENABLED=false
|
ENV INIT_ENABLED=false
|
||||||
|
ENV DISABLE_IPV6=false
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source=https://github.com/wg-easy/wg-easy
|
LABEL org.opencontainers.image.source=https://github.com/wg-easy/wg-easy
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ ENV PORT=51821
|
|||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV INSECURE=true
|
ENV INSECURE=true
|
||||||
ENV INIT_ENABLED=false
|
ENV INIT_ENABLED=false
|
||||||
|
ENV DISABLE_IPV6=false
|
||||||
|
|
||||||
# Install Dependencies
|
# Install Dependencies
|
||||||
COPY src/package.json src/pnpm-lock.yaml ./
|
COPY src/package.json src/pnpm-lock.yaml ./
|
||||||
@@ -36,3 +37,4 @@ RUN pnpm install
|
|||||||
# Copy Project
|
# Copy Project
|
||||||
COPY src ./
|
COPY src ./
|
||||||
|
|
||||||
|
ENTRYPOINT [ "pnpm", "run" ]
|
||||||
|
|||||||
@@ -7,18 +7,12 @@
|
|||||||
[](https://github.com/wg-easy/wg-easy/releases/latest)
|
[](https://github.com/wg-easy/wg-easy/releases/latest)
|
||||||
[](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy)
|
[](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy)
|
||||||
|
|
||||||
<!-- TODO: remove after release -->
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> You are viewing the README of the pre-release of v15.
|
|
||||||
> If you want to setup wg-easy right now. Read the README in the production branch here: [README](https://github.com/wg-easy/wg-easy/tree/production) or here for the last nightly: [README](https://github.com/wg-easy/wg-easy/tree/c6dce0f6fb2e28e7e40ddac1498bd67e9bb17cba)
|
|
||||||
|
|
||||||
You have found the easiest way to install & manage WireGuard on any Linux host!
|
You have found the easiest way to install & manage WireGuard on any Linux host!
|
||||||
|
|
||||||
<!-- TOOD: update screenshot -->
|
<!-- TOOD: update screenshot -->
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./assets/screenshot.png" width="802" />
|
<img src="./assets/screenshot.png" width="802" alt="wg-easy Screenshot" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -43,11 +37,6 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> To better manage documentation for this project, it has its own site here: [https://wg-easy.github.io/wg-easy/latest](https://wg-easy.github.io/wg-easy/latest)
|
> To better manage documentation for this project, it has its own site here: [https://wg-easy.github.io/wg-easy/latest](https://wg-easy.github.io/wg-easy/latest)
|
||||||
|
|
||||||
<!-- TODO: remove after release -->
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> As the Docs are still in Pre-release, you can access them here [https://wg-easy.github.io/wg-easy/Pre-release](https://wg-easy.github.io/wg-easy/Pre-release)
|
|
||||||
|
|
||||||
- [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/)
|
- [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/)
|
||||||
- [Basic Installation](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/)
|
- [Basic Installation](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/)
|
||||||
- [Caddy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/caddy/)
|
- [Caddy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/caddy/)
|
||||||
@@ -81,7 +70,7 @@ 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 download [`docker-compose.yml`](docker-compose.yml) and execute `sudo docker compose up -d`.
|
||||||
|
|
||||||
Now setup a reverse proxy to be able to access the Web UI from the internet.
|
Now setup a reverse proxy to be able to access the Web UI securely from the internet.
|
||||||
|
|
||||||
If you want to access the Web UI over HTTP, change the env var `INSECURE` to `true`. This is not recommended. Only use this for testing
|
If you want to access the Web UI over HTTP, change the env var `INSECURE` to `true`. This is not recommended. Only use this for testing
|
||||||
|
|
||||||
@@ -116,6 +105,15 @@ If you add something that should be auto-importable and VSCode complains, run:
|
|||||||
```shell
|
```shell
|
||||||
cd src
|
cd src
|
||||||
pnpm install
|
pnpm install
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Cli
|
||||||
|
|
||||||
|
This starts the cli with docker
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm cli:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 185 KiB |
@@ -2,7 +2,7 @@ services:
|
|||||||
wg-easy:
|
wg-easy:
|
||||||
build:
|
build:
|
||||||
dockerfile: ./Dockerfile.dev
|
dockerfile: ./Dockerfile.dev
|
||||||
command: pnpm run dev
|
command: dev
|
||||||
volumes:
|
volumes:
|
||||||
- ./src/:/app/
|
- ./src/:/app/
|
||||||
- temp:/app/.nuxt/
|
- temp:/app/.nuxt/
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ services:
|
|||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
- SYS_MODULE
|
- SYS_MODULE
|
||||||
# - NET_RAW # ⚠️ Uncomment if using Podman Compose
|
# - NET_RAW # ⚠️ Uncomment if using Podman
|
||||||
sysctls:
|
sysctls:
|
||||||
- net.ipv4.ip_forward=1
|
- net.ipv4.ip_forward=1
|
||||||
- net.ipv4.conf.all.src_valid_mark=1
|
- net.ipv4.conf.all.src_valid_mark=1
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
@@ -2,6 +2,11 @@
|
|||||||
title: API
|
title: API
|
||||||
---
|
---
|
||||||
|
|
||||||
|
/// warning | Breaking Changes
|
||||||
|
|
||||||
|
This API is not yet stable and may change in the future. The API is currently in development and is subject to change without notice. The API is not yet documented, but we will add documentation as the API stabilizes.
|
||||||
|
///
|
||||||
|
|
||||||
You can use the API to interact with the application programmatically. The API is available at `/api` and supports both GET and POST requests. The API is designed to be simple and easy to use, with a focus on providing a consistent interface for all endpoints.
|
You can use the API to interact with the application programmatically. The API is available at `/api` and supports both GET and POST requests. The API is designed to be simple and easy to use, with a focus on providing a consistent interface for all endpoints.
|
||||||
|
|
||||||
There is no documentation for the API yet, but this will be added as the underlying library supports it.
|
There is no documentation for the API yet, but this will be added as the underlying library supports it.
|
||||||
|
|||||||
@@ -5,7 +5,18 @@ title: Optional Configuration
|
|||||||
You can set these environment variables to configure the container. They are not required, but can be useful in some cases.
|
You can set these environment variables to configure the container. They are not required, but can be useful in some cases.
|
||||||
|
|
||||||
| Env | Default | Example | Description |
|
| Env | Default | Example | Description |
|
||||||
| ---------- | --------- | ----------- | ------------------------------ |
|
| -------------- | --------- | ----------- | ---------------------------------- |
|
||||||
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
|
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
|
||||||
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
|
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
|
||||||
| `INSECURE` | `false` | `true` | If access over http is allowed |
|
| `INSECURE` | `false` | `true` | If access over http is allowed |
|
||||||
|
| `DISABLE_IPV6` | `false` | `true` | If IPv6 support should be disabled |
|
||||||
|
|
||||||
|
/// note | IPv6 Caveats
|
||||||
|
|
||||||
|
Disabling IPv6 will disable the creation of the default IPv6 firewall rules and won't add a IPv6 address to the interface and clients.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
///
|
||||||
|
|||||||
@@ -16,15 +16,15 @@ You need to add a scrape config to your Prometheus configuration file. Here is a
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
scrape_configs:
|
scrape_configs:
|
||||||
- job_name: "wg-easy"
|
- job_name: 'wg-easy'
|
||||||
scrape_interval: 30s
|
scrape_interval: 30s
|
||||||
metrics_path: /metrics/prometheus
|
metrics_path: /metrics/prometheus
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets:
|
- targets:
|
||||||
- "localhost:51821"
|
- 'localhost:51821'
|
||||||
authorization:
|
authorization:
|
||||||
type: Bearer
|
type: Bearer
|
||||||
credentials: "SuperSecurePassword"
|
credentials: 'SuperSecurePassword'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Grafana Dashboard
|
## Grafana Dashboard
|
||||||
|
|||||||
@@ -6,22 +6,24 @@ This guide will help you migrate from `v14` to version `v15` of `wg-easy`.
|
|||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
|
|
||||||
- This is a complete rewrite of the `wg-easy` project. Therefore the configuration files and the way you interact with the project have changed.
|
- This is a complete rewrite of the `wg-easy` project, therefore the configuration files and the way you interact with the project have changed.
|
||||||
- If you use armv6 or armv7, you can't migrate to `v15` yet. We are working on it.
|
- If you use armv6, you unfortunately won't be able to migrate to `v15`.
|
||||||
- If you are connecting to the web ui via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container.
|
- If you are connecting to the Web UI via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container.
|
||||||
|
|
||||||
## Migration
|
## Migration
|
||||||
|
|
||||||
### Backup
|
### Backup
|
||||||
|
|
||||||
Before you start the migration, make sure to backup your existing configuration files.
|
Before you start the migration, make sure to back up your existing configuration files.
|
||||||
|
|
||||||
Go into the Web Ui and click the Backup button, this should download a `wg0.json` file.
|
Go into the Web UI and click the Backup button, this should download a `wg0.json` file.
|
||||||
|
|
||||||
Or download the `wg0.json` file from your container volume to your pc.
|
Or download the `wg0.json` file from your container volume to your pc.
|
||||||
|
|
||||||
You will need this file for the migration
|
You will need this file for the migration
|
||||||
|
|
||||||
|
You will also need to back up the old environment variables you set for the container, as they will not be automatically migrated.
|
||||||
|
|
||||||
### Remove old container
|
### Remove old container
|
||||||
|
|
||||||
1. Stop the running container
|
1. Stop the running container
|
||||||
@@ -32,21 +34,25 @@ If you are using `docker run`
|
|||||||
docker stop wg-easy
|
docker stop wg-easy
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are using `docker-compose`
|
If you are using `docker compose`
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker-compose down
|
docker compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
### Start new container
|
### Start new container
|
||||||
|
|
||||||
Follow the instructions in the [Getting Started][docs-getting-started] or [Basic Installation][docs-examples] guide to start the new container.
|
Follow the instructions in the [Getting Started][docs-getting-started] or [Basic Installation][docs-examples] guide to start the new container.
|
||||||
|
|
||||||
In the setup wizard, select that you already already have a configuration file and upload the `wg0.json` file you downloaded in the backup step.
|
In the setup wizard, select that you already have a configuration file and upload the `wg0.json` file you downloaded in the backup step.
|
||||||
|
|
||||||
[docs-getting-started]: ../../getting-started.md
|
[docs-getting-started]: ../../getting-started.md
|
||||||
[docs-examples]: ../../examples/tutorials/basic-installation.md
|
[docs-examples]: ../../examples/tutorials/basic-installation.md
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
### Done
|
### Done
|
||||||
|
|
||||||
You have now successfully migrated to `v15` of `wg-easy`.
|
You have now successfully migrated to `v15` of `wg-easy`.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ When refactoring, writing or altering files, adhere to these rules:
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Make sure to select `nightly` in the dropdown menu at the top. Navigate to the page you would like to edit and click the edit button in the top right. This allows you to make changes and create a pull-request.
|
Make sure to select `edge` in the dropdown menu at the top. Navigate to the page you would like to edit and click the edit button in the top right. This allows you to make changes and create a pull-request.
|
||||||
|
|
||||||
Alternatively you can make the changes locally. For that you'll need to have Docker installed. Run
|
Alternatively you can make the changes locally. For that you'll need to have Docker installed. Run
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ The development workflow is the following:
|
|||||||
3. Document your improvements if necessary
|
3. Document your improvements if necessary
|
||||||
4. [Commit][commit] (and [sign your commit][gpg]), push and create a pull-request to merge into `master`. Please **use the pull-request template** to provide a minimum of contextual information and make sure to meet the requirements of the checklist.
|
4. [Commit][commit] (and [sign your commit][gpg]), push and create a pull-request to merge into `master`. Please **use the pull-request template** to provide a minimum of contextual information and make sure to meet the requirements of the checklist.
|
||||||
|
|
||||||
Pull requests are automatically tested against the CI and will be reviewed when tests pass. When your changes are validated, your branch is merged. CI builds the new `:nightly` image every night and your changes will be includes in the next version release.
|
Pull requests are automatically tested against the CI and will be reviewed when tests pass. When your changes are validated, your branch is merged. CI builds the new `:edge` image on every push to the `master` branch and your changes will be included in the next version release.
|
||||||
|
|
||||||
[docs-latest]: https://wg-easy.github.io/wg-easy/latest
|
[docs-latest]: https://wg-easy.github.io/wg-easy/latest
|
||||||
[github-file-readme]: https://github.com/wg-easy/wg-easy/blob/master/README.md
|
[github-file-readme]: https://github.com/wg-easy/wg-easy/blob/master/README.md
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: Translation
|
||||||
|
---
|
||||||
|
|
||||||
|
This project supports multiple languages. If you would like to contribute a translation, please follow these steps:
|
||||||
|
|
||||||
|
## Add new Translation
|
||||||
|
|
||||||
|
Create a new file in `src/i18n/locales`. Name it `<locale_code>.json` (e.g. `fr.json` for French).
|
||||||
|
|
||||||
|
Import and add the newly created file in `src/i18n/i18n.config.ts`.
|
||||||
|
|
||||||
|
Add your language in the `src/nuxt.config.ts` file. You have to specify code, language and name.
|
||||||
|
|
||||||
|
`code` is the name of the translation file without the extension (e.g. `fr` for `fr.json`).
|
||||||
|
|
||||||
|
`language` is the BCP 47 language tag with region (e.g. `fr-FR` for French). See [www.lingoes.net](http://www.lingoes.net/en/translator/langcode.htm) for a list of language codes.
|
||||||
|
|
||||||
|
`name` is the display name of the language (e.g. `Français` for French).
|
||||||
|
|
||||||
|
## Update existing Translation
|
||||||
|
|
||||||
|
If you need to update an existing translation, simply edit the corresponding `<locale_code>.json` file in `src/i18n/locales`.
|
||||||
|
|
||||||
|
## Contribute changes
|
||||||
|
|
||||||
|
See [Pull Requests](./issues-and-pull-requests.md#pull-requests) on how to contribute your translation.
|
||||||
@@ -8,7 +8,7 @@ title: Basic Installation
|
|||||||
|
|
||||||
1. You need to have a host that you can manage
|
1. You need to have a host that you can manage
|
||||||
2. You need to have a domain name or a public IP address
|
2. You need to have a domain name or a public IP address
|
||||||
3. You need a supported architecture (x86_64, arm64)
|
3. You need a supported architecture (x86_64, arm64, armv7)
|
||||||
4. You need curl installed on your host
|
4. You need curl installed on your host
|
||||||
|
|
||||||
## Install Docker
|
## Install Docker
|
||||||
@@ -33,7 +33,7 @@ Follow the Docs here: <https://docs.docker.com/engine/install/> and install Dock
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
cd /etc/docker/containers/wg-easy
|
cd /etc/docker/containers/wg-easy
|
||||||
sudo docker-compose up -d
|
sudo docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Setup Firewall
|
## Setup Firewall
|
||||||
@@ -48,6 +48,7 @@ These ports can be changed, so if you change them you have to update your firewa
|
|||||||
|
|
||||||
- To setup traefik follow the instructions here: [Traefik](./traefik.md)
|
- To setup traefik follow the instructions here: [Traefik](./traefik.md)
|
||||||
- To setup caddy follow the instructions here: [Caddy](./caddy.md)
|
- To setup caddy follow the instructions here: [Caddy](./caddy.md)
|
||||||
|
- If you do not want to use a reverse proxy follow the instructions here: [No Reverse Proxy](./reverse-proxyless.md)
|
||||||
|
|
||||||
## Update `wg-easy`
|
## Update `wg-easy`
|
||||||
|
|
||||||
@@ -55,8 +56,8 @@ To update `wg-easy` to the latest version, run:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
cd /etc/docker/containers/wg-easy
|
cd /etc/docker/containers/wg-easy
|
||||||
sudo docker-compose pull
|
sudo docker compose pull
|
||||||
sudo docker-compose up -d
|
sudo docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Auto Update
|
## Auto Update
|
||||||
|
|||||||
@@ -2,8 +2,101 @@
|
|||||||
title: Caddy
|
title: Caddy
|
||||||
---
|
---
|
||||||
|
|
||||||
It seems like the Docs on how to setup Caddy are not available yet.
|
/// note | Opinionated
|
||||||
|
|
||||||
Feel free to create a PR and add them here.
|
This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths.
|
||||||
|
///
|
||||||
|
|
||||||
<!-- TODO -->
|
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.
|
||||||
|
|
||||||
|
## Create a docker composition for `caddy`
|
||||||
|
|
||||||
|
```txt
|
||||||
|
.
|
||||||
|
├── compose.yml
|
||||||
|
└── Caddyfile
|
||||||
|
|
||||||
|
1 directory, 2 files
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# compose.yml
|
||||||
|
|
||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
container_name: caddy
|
||||||
|
image: caddy:2.10.0-alpine
|
||||||
|
# publish everything you deem necessary
|
||||||
|
ports:
|
||||||
|
- '80:80/tcp'
|
||||||
|
- '443:443/tcp'
|
||||||
|
- '443:443/udp'
|
||||||
|
networks:
|
||||||
|
- caddy
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- './Caddyfile:/etc/caddy/Caddyfile:ro'
|
||||||
|
- config:/config
|
||||||
|
- data:/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
caddy:
|
||||||
|
name: caddy
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
config:
|
||||||
|
data:
|
||||||
|
```
|
||||||
|
|
||||||
|
```txt
|
||||||
|
# Caddyfile
|
||||||
|
|
||||||
|
{
|
||||||
|
# setup your email address
|
||||||
|
email mail@example.com
|
||||||
|
}
|
||||||
|
|
||||||
|
wg-easy.example.com {
|
||||||
|
# since the container will share the network with wg-easy
|
||||||
|
# we can use the proper container name
|
||||||
|
reverse_proxy wg-easy:80
|
||||||
|
tls internal
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...and start it with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adapt the docker composition of `wg-easy`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
wg-easy:
|
||||||
|
# sync container name and port according to Caddyfile
|
||||||
|
container_name: wg-easy
|
||||||
|
environment:
|
||||||
|
- PORT=80
|
||||||
|
# no need to publish the HTTP server anymore
|
||||||
|
ports:
|
||||||
|
- "51820:51820/udp"
|
||||||
|
# add to caddy network
|
||||||
|
networks:
|
||||||
|
caddy:
|
||||||
|
...
|
||||||
|
|
||||||
|
networks:
|
||||||
|
caddy:
|
||||||
|
external: true
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
...and restart it with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now access `wg-easy` at [https://wg-easy.example.com](https://wg-easy.example.com) and start the setup.
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
title: No Reverse Proxy
|
||||||
|
---
|
||||||
|
|
||||||
|
/// warning | Insecure
|
||||||
|
|
||||||
|
This is insecure. You should use a reverse proxy to secure the connection.
|
||||||
|
|
||||||
|
Only use this method if you know what you are doing.
|
||||||
|
///
|
||||||
|
|
||||||
|
If you only allow access to the web UI from your local network, you can skip the reverse proxy setup. This is not recommended, but it is possible.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
- Edit the `docker-compose.yml` file and uncomment `environment` and `INSECURE`
|
||||||
|
|
||||||
|
- Set `INSECURE` to `true` to allow access to the web UI over a non-secure connection.
|
||||||
|
|
||||||
|
- The `docker-compose.yml` file should look something like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- INSECURE=true
|
||||||
|
```
|
||||||
|
|
||||||
|
- Save the file and restart `wg-easy`.
|
||||||
|
|
||||||
|
- Make sure that the Web UI is not accessible from outside your local network.
|
||||||
@@ -25,9 +25,9 @@ services:
|
|||||||
container_name: traefik
|
container_name: traefik
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- '80:80'
|
||||||
- "443:443/tcp"
|
- '443:443/tcp'
|
||||||
- "443:443/udp"
|
- '443:443/udp'
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /etc/docker/volumes/traefik/traefik.yml:/traefik.yml:ro
|
- /etc/docker/volumes/traefik/traefik.yml:/traefik.yml:ro
|
||||||
@@ -51,14 +51,14 @@ log:
|
|||||||
|
|
||||||
entryPoints:
|
entryPoints:
|
||||||
web:
|
web:
|
||||||
address: ":80/tcp"
|
address: ':80/tcp'
|
||||||
http:
|
http:
|
||||||
redirections:
|
redirections:
|
||||||
entryPoint:
|
entryPoint:
|
||||||
to: websecure
|
to: websecure
|
||||||
scheme: https
|
scheme: https
|
||||||
websecure:
|
websecure:
|
||||||
address: ":443/tcp"
|
address: ':443/tcp'
|
||||||
http:
|
http:
|
||||||
middlewares:
|
middlewares:
|
||||||
- compress@file
|
- compress@file
|
||||||
@@ -100,7 +100,7 @@ http:
|
|||||||
services:
|
services:
|
||||||
basicAuth:
|
basicAuth:
|
||||||
users:
|
users:
|
||||||
- "$username$:$password$"
|
- '$username$:$password$'
|
||||||
compress:
|
compress:
|
||||||
compress: {}
|
compress: {}
|
||||||
hsts:
|
hsts:
|
||||||
@@ -141,7 +141,7 @@ sudo docker network create traefik
|
|||||||
## Start traefik
|
## Start traefik
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo docker-compose up -d
|
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 no access the Traefik dashboard at `https://traefik.$example.com$` with the credentials you set in `traefik_dynamic.yml`.
|
||||||
@@ -178,7 +178,7 @@ networks:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
cd /etc/docker/containers/wg-easy
|
cd /etc/docker/containers/wg-easy
|
||||||
sudo docker-compose up -d
|
sudo docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now access `wg-easy` at `https://wg-easy.$example.com$` and start the setup.
|
You can now access `wg-easy` at `https://wg-easy.$example.com$` and start the setup.
|
||||||
|
|||||||
+10
-10
@@ -14,13 +14,13 @@ To resolve this issue, you can try the following steps:
|
|||||||
|
|
||||||
1. **Load the WireGuard kernel module**: If the WireGuard kernel module is not loaded, you can load it manually by running:
|
1. **Load the WireGuard kernel module**: If the WireGuard kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
sudo modprobe wireguard
|
sudo modprobe wireguard
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Load the WireGuard kernel module on boot**: If you want to ensure that the WireGuard kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
2. **Load the WireGuard kernel module on boot**: If you want to ensure that the WireGuard kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
echo "wireguard" | sudo tee -a /etc/modules
|
echo "wireguard" | sudo tee -a /etc/modules
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -32,13 +32,13 @@ To resolve this issue, you can try the following steps:
|
|||||||
|
|
||||||
1. **Load the `nat` kernel module**: If the `nat` kernel module is not loaded, you can load it manually by running:
|
1. **Load the `nat` kernel module**: If the `nat` kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
sudo modprobe iptable_nat
|
sudo modprobe iptable_nat
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Load the `nat` kernel module on boot**: If you want to ensure that the `nat` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
2. **Load the `nat` kernel module on boot**: If you want to ensure that the `nat` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
echo "iptable_nat" | sudo tee -a /etc/modules
|
echo "iptable_nat" | sudo tee -a /etc/modules
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -50,13 +50,13 @@ To resolve this issue, you can try the following steps:
|
|||||||
|
|
||||||
1. **Load the `nat` kernel module**: If the `nat` kernel module is not loaded, you can load it manually by running:
|
1. **Load the `nat` kernel module**: If the `nat` kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
sudo modprobe ip6table_nat
|
sudo modprobe ip6table_nat
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Load the `nat` kernel module on boot**: If you want to ensure that the `nat` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
2. **Load the `nat` kernel module on boot**: If you want to ensure that the `nat` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
echo "ip6table_nat" | sudo tee -a /etc/modules
|
echo "ip6table_nat" | sudo tee -a /etc/modules
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -68,13 +68,13 @@ To resolve this issue, you can try the following steps:
|
|||||||
|
|
||||||
1. **Load the `filter` kernel module**: If the `filter` kernel module is not loaded, you can load it manually by running:
|
1. **Load the `filter` kernel module**: If the `filter` kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
sudo modprobe iptable_filter
|
sudo modprobe iptable_filter
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Load the `filter` kernel module on boot**: If you want to ensure that the `filter` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
2. **Load the `filter` kernel module on boot**: If you want to ensure that the `filter` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
echo "iptable_filter" | sudo tee -a /etc/modules
|
echo "iptable_filter" | sudo tee -a /etc/modules
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -86,12 +86,12 @@ To resolve this issue, you can try the following steps:
|
|||||||
|
|
||||||
1. **Load the `filter` kernel module**: If the `filter` kernel module is not loaded, you can load it manually by running:
|
1. **Load the `filter` kernel module**: If the `filter` kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
sudo modprobe ip6table_filter
|
sudo modprobe ip6table_filter
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Load the `filter` kernel module on boot**: If you want to ensure that the `filter` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
2. **Load the `filter` kernel module on boot**: If you want to ensure that the `filter` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
echo "ip6table_filter" | sudo tee -a /etc/modules
|
echo "ip6table_filter" | sudo tee -a /etc/modules
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Before you can get started with deploying your own VPN, there are some requireme
|
|||||||
|
|
||||||
1. You need to have a host that you can manage
|
1. You need to have a host that you can manage
|
||||||
2. You need to have a domain name or a public IP address
|
2. You need to have a domain name or a public IP address
|
||||||
3. You need a supported architecture (x86_64, arm64)
|
3. You need a supported architecture (x86_64, arm64, armv7)
|
||||||
|
|
||||||
### Host Setup
|
### Host Setup
|
||||||
|
|
||||||
@@ -42,13 +42,15 @@ To understand which tags you should use, read this section carefully. [Our CI][g
|
|||||||
All workflows are using the tagging convention listed below. It is subsequently applied to all images.
|
All workflows are using the tagging convention listed below. It is subsequently applied to all images.
|
||||||
|
|
||||||
| tag | Type | Example | Description |
|
| tag | Type | Example | Description |
|
||||||
| ------------- | ---------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
| ------------- | ------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||||
| `15` | latest minor for that major tag | `ghcr.io/wg-easy/wg-easy:15` | latest features for specific major versions, no breaking changes |
|
| `15` | latest minor for that major tag | `ghcr.io/wg-easy/wg-easy:15` | latest features for specific major versions, no breaking changes, recommended |
|
||||||
| `latest` | latest tag | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | stable as possible get bug fixes quickly when needed, see Releases for more information. |
|
| `latest` | latest tag | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | points to latest release, can include breaking changes |
|
||||||
| `15.0` | latest patch for that minor tag | `ghcr.io/wg-easy/wg-easy:15.0` | latest patches for specific minor version |
|
| `15.0` | latest patch for that minor tag | `ghcr.io/wg-easy/wg-easy:15.0` | latest patches for specific minor version |
|
||||||
| `15.0.0` | specific tag | `ghcr.io/wg-easy/wg-easy:15.0.0` | specific release, don't use this as this will not get updated |
|
| `15.0.0` | specific tag | `ghcr.io/wg-easy/wg-easy:15.0.0` | specific release, no updates |
|
||||||
| `nightly` | [`master`](https://github.com/wg-easy/wg-easy/tree/master) | `ghcr.io/wg-easy/wg-easy:nightly` | mostly unstable gets frequent package and code updates, deployed against [`master`](https://github.com/wg-easy/wg-easy/tree/master). |
|
| `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 before landing into [`master`](https://github.com/wg-easy/wg-easy/tree/master). |
|
| `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs |
|
||||||
|
|
||||||
|
<!-- ref: major version -->
|
||||||
|
|
||||||
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`).
|
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`).
|
||||||
|
|
||||||
@@ -56,41 +58,15 @@ When publishing a tag we follow the [Semantic Versioning][semver] specification.
|
|||||||
[ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy
|
[ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy
|
||||||
[semver]: https://semver.org/
|
[semver]: https://semver.org/
|
||||||
|
|
||||||
### Get All Files
|
### Follow tutorials
|
||||||
|
|
||||||
Issue the following command to acquire the necessary file:
|
- [Basic Installation with Docker Compose (Recommended)](./examples/tutorials/basic-installation.md)
|
||||||
|
- [Simple Installation with Docker Run](./examples/tutorials/docker-run.md)
|
||||||
|
- [Advanced Installation with Podman](./examples/tutorials/podman-nft.md)
|
||||||
|
|
||||||
```shell
|
/// danger | Use the Correct Commands For Stopping and Starting `wg-easy`
|
||||||
wget "https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Start the Container
|
|
||||||
|
|
||||||
To start the container, issue the following command:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration Steps
|
|
||||||
|
|
||||||
Now follow the setup process in your web browser
|
|
||||||
|
|
||||||
### Stopping the Container
|
|
||||||
|
|
||||||
To stop the container, issue the following command:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo docker compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
/// danger | Using the Correct Commands For Stopping and Starting `wg-easy`
|
|
||||||
|
|
||||||
**Use `sudo docker compose up / down`, not `sudo docker compose start / stop`**. Otherwise, the container is not properly destroyed and you may experience problems during startup because of inconsistent state.
|
**Use `sudo docker compose up / down`, not `sudo docker compose start / stop`**. Otherwise, the container is not properly destroyed and you may experience problems during startup because of inconsistent state.
|
||||||
///
|
///
|
||||||
|
|
||||||
**That's it! It really is that easy**.
|
**That's it! It really is that easy**.
|
||||||
|
|
||||||
If you need more help you can read the [Basic Installation Tutorial][basic-installation].
|
|
||||||
|
|
||||||
[basic-installation]: ./examples/tutorials/basic-installation.md
|
|
||||||
|
|||||||
@@ -2,4 +2,25 @@
|
|||||||
title: 2FA
|
title: 2FA
|
||||||
---
|
---
|
||||||
|
|
||||||
TODO
|
The user can enable 2FA from the Account page. The Account page is accessible from the dropdown menu in the top right corner of the application.
|
||||||
|
|
||||||
|
## Enable TOTP
|
||||||
|
|
||||||
|
- **Enable Two Factor Authentication**: Enable TOTP for the user.
|
||||||
|
|
||||||
|
## Configure TOTP
|
||||||
|
|
||||||
|
A QR code will be displayed. Scan the QR code with your TOTP application (e.g., Google Authenticator, Authy, etc.) to add the account.
|
||||||
|
|
||||||
|
To verify that the TOTP key is working, the user must enter the TOTP code generated by the TOTP application.
|
||||||
|
|
||||||
|
- **TOTP Key**: The TOTP key for the user. This key is used to generate the TOTP code.
|
||||||
|
- **TOTP Code**: The current TOTP code for the user. This code is used to verify the TOTP key.
|
||||||
|
- **Enable Two Factor Authentication**: Enable TOTP for the user.
|
||||||
|
|
||||||
|
## Disable TOTP
|
||||||
|
|
||||||
|
To disable TOTP, the user must enter the current password.
|
||||||
|
|
||||||
|
- **Current Password**: The current password of the user.
|
||||||
|
- **Disable Two Factor Authentication**: Disable TOTP for the user.
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: Edit Account
|
|
||||||
---
|
|
||||||
|
|
||||||
TODO
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
title: CLI
|
||||||
|
---
|
||||||
|
|
||||||
|
If you want to use the CLI, you can run it with
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/wg-easy
|
||||||
|
docker compose exec -it wg-easy cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Run
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run --rm -it \
|
||||||
|
-v ~/.wg-easy:/etc/wireguard \
|
||||||
|
ghcr.io/wg-easy/wg-easy:15 \
|
||||||
|
cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reset Password
|
||||||
|
|
||||||
|
If you want to reset the password for the admin user, you can run the following command:
|
||||||
|
|
||||||
|
#### By Prompt
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/wg-easy
|
||||||
|
docker compose exec -it wg-easy cli db:admin:reset
|
||||||
|
```
|
||||||
|
|
||||||
|
You are asked to provide the new password
|
||||||
|
|
||||||
|
#### By Argument
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/wg-easy
|
||||||
|
docker compose exec -it wg-easy cli db:admin:reset --password <new_password>
|
||||||
|
```
|
||||||
|
|
||||||
|
This will reset the password for the admin user to the new password you provided. If you include special characters in the password, make sure to escape them properly.
|
||||||
@@ -2,4 +2,49 @@
|
|||||||
title: Edit Client
|
title: Edit Client
|
||||||
---
|
---
|
||||||
|
|
||||||
TODO
|
## General
|
||||||
|
|
||||||
|
- **Name**: The name of the client.
|
||||||
|
- **Enabled**: Whether the client can connect to the VPN.
|
||||||
|
- **Expire Date**: The date the client will be disabled.
|
||||||
|
|
||||||
|
## Address
|
||||||
|
|
||||||
|
- **IPv4**: The IPv4 address of the client.
|
||||||
|
- **IPv6**: The IPv6 address of the client.
|
||||||
|
|
||||||
|
## Allowed IPs
|
||||||
|
|
||||||
|
Which IPs will be routed through the VPN.
|
||||||
|
|
||||||
|
This will not prevent the user from modifying it locally and accessing IP ranges that they should not be able to access.
|
||||||
|
|
||||||
|
Use firewall rules to prevent access to IP ranges that the user should not be able to access.
|
||||||
|
|
||||||
|
## Server Allowed IPs
|
||||||
|
|
||||||
|
Which IPs will be routed to the client.
|
||||||
|
|
||||||
|
## DNS
|
||||||
|
|
||||||
|
The DNS server that the client will use.
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
- **MTU**: The maximum transmission unit for the client.
|
||||||
|
- **Persistent Keepalive**: The interval for sending keepalive packets to the server.
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
This can only be used for clients that use `wg-quick`. Setting this will throw a error when importing the config on other clients.
|
||||||
|
|
||||||
|
- **PreUp**: Commands to run before the interface is brought up.
|
||||||
|
- **PostUp**: Commands to run after the interface is brought up.
|
||||||
|
- **PreDown**: Commands to run before the interface is brought down.
|
||||||
|
- **PostDown**: Commands to run after the interface is brought down.
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
- **Save**: Save the changes made in the form.
|
||||||
|
- **Revert**: Revert the changes made in the form.
|
||||||
|
- **Delete**: Delete the client.
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
title: Login
|
|
||||||
---
|
|
||||||
|
|
||||||
TODO
|
|
||||||
@@ -2,4 +2,23 @@
|
|||||||
title: Setup
|
title: Setup
|
||||||
---
|
---
|
||||||
|
|
||||||
TODO
|
## User Setup
|
||||||
|
|
||||||
|
- **Username**: The username of the user.
|
||||||
|
- **Password**: The password of the user.
|
||||||
|
- **Confirm Password**: The password of the user.
|
||||||
|
|
||||||
|
## Existing Setup
|
||||||
|
|
||||||
|
If you have the config from the previous version, you can import it by clicking "Yes". This currently expects a config from v14.
|
||||||
|
|
||||||
|
If this is the first time you are using this, you can click "No" to create a new config.
|
||||||
|
|
||||||
|
### No - Host Setup
|
||||||
|
|
||||||
|
- **Host**: The host of the server. The clients will connect to this address. This can be a domain name or an IP address. Make sure to wrap it in brackets if it is an IPv6 address. For example: `[::1]` or `[2001:db8::1]`.
|
||||||
|
- **Port**: The port of the server. The clients will connect to this port. The server will listen on this port.
|
||||||
|
|
||||||
|
### Yes - Migration
|
||||||
|
|
||||||
|
Select the `wg0.json` file from the previous version. Read [Migrate from v14 to v15](../advanced/migrate/from-14-to-15.md) for more information.
|
||||||
|
|||||||
+7
-7
@@ -1,6 +1,6 @@
|
|||||||
site_name: "wg-easy"
|
site_name: 'wg-easy'
|
||||||
site_description: "The easiest way to run WireGuard VPN + Web-based Admin UI."
|
site_description: 'The easiest way to run WireGuard VPN + Web-based Admin UI.'
|
||||||
site_author: "WireGuard Easy"
|
site_author: 'WireGuard Easy'
|
||||||
copyright: >
|
copyright: >
|
||||||
<p>
|
<p>
|
||||||
© <a href="https://github.com/wg-easy"><em>Wireguard Easy</em></a><br/>
|
© <a href="https://github.com/wg-easy"><em>Wireguard Easy</em></a><br/>
|
||||||
@@ -12,9 +12,9 @@ copyright: >
|
|||||||
repo_url: https://github.com/wg-easy/wg-easy
|
repo_url: https://github.com/wg-easy/wg-easy
|
||||||
repo_name: wg-easy
|
repo_name: wg-easy
|
||||||
|
|
||||||
edit_uri: "edit/master/docs/content"
|
edit_uri: 'edit/master/docs/content'
|
||||||
|
|
||||||
docs_dir: "content/"
|
docs_dir: 'content/'
|
||||||
|
|
||||||
site_url: https://wg-easy.github.io/wg-easy
|
site_url: https://wg-easy.github.io/wg-easy
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ theme:
|
|||||||
- content.code.annotate
|
- content.code.annotate
|
||||||
palette:
|
palette:
|
||||||
# Light mode
|
# Light mode
|
||||||
- media: "(prefers-color-scheme: light)"
|
- media: '(prefers-color-scheme: light)'
|
||||||
scheme: default
|
scheme: default
|
||||||
primary: grey
|
primary: grey
|
||||||
accent: red
|
accent: red
|
||||||
@@ -42,7 +42,7 @@ theme:
|
|||||||
icon: material/weather-night
|
icon: material/weather-night
|
||||||
name: Switch to dark mode
|
name: Switch to dark mode
|
||||||
# Dark mode
|
# Dark mode
|
||||||
- media: "(prefers-color-scheme: dark)"
|
- media: '(prefers-color-scheme: dark)'
|
||||||
scheme: slate
|
scheme: slate
|
||||||
primary: grey
|
primary: grey
|
||||||
accent: red
|
accent: red
|
||||||
|
|||||||
+8
-3
@@ -2,10 +2,15 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "docker compose -f docker-compose.dev.yml up --build",
|
"dev": "docker compose -f docker-compose.dev.yml up wg-easy --build",
|
||||||
|
"cli:dev": "docker compose -f docker-compose.dev.yml run --build --rm -it wg-easy cli:dev",
|
||||||
"build": "docker build -t wg-easy .",
|
"build": "docker build -t wg-easy .",
|
||||||
"docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8080",
|
"docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8080",
|
||||||
"scripts:version": "bash scripts/version.sh"
|
"scripts:version": "bash scripts/version.sh",
|
||||||
|
"format:check:docs": "prettier --check docs"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.8.0"
|
"devDependencies": {
|
||||||
|
"prettier": "^3.6.2"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.12.4"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+16
-1
@@ -6,4 +6,19 @@ settings:
|
|||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.: {}
|
.:
|
||||||
|
devDependencies:
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.6.2
|
||||||
|
version: 3.6.2
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
prettier@3.6.2:
|
||||||
|
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
prettier@3.6.2: {}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
public-hoist-pattern[]=@libsql/linux*
|
|
||||||
@@ -10,12 +10,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton @click="$emit('change', ipv4Cidr, ipv6Cidr)">
|
<BasePrimaryButton @click="$emit('change', ipv4Cidr, ipv6Cidr)">
|
||||||
{{ $t('dialog.change') }}
|
{{ $t('dialog.change') }}
|
||||||
</BaseButton>
|
</BasePrimaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</template>
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton @click="$emit('restart')">
|
<BasePrimaryButton @click="$emit('restart')">
|
||||||
{{ $t('admin.interface.restart') }}
|
{{ $t('admin.interface.restart') }}
|
||||||
</BaseButton>
|
</BasePrimaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</template>
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|||||||
@@ -13,12 +13,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton @click="$emit('change', selected)">
|
<BasePrimaryButton @click="$emit('change', selected)">
|
||||||
{{ $t('dialog.change') }}
|
{{ $t('dialog.change') }}
|
||||||
</BaseButton>
|
</BasePrimaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</template>
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="elementType"
|
||||||
|
role="button"
|
||||||
|
class="inline-flex items-center rounded border-2 border-red-800 bg-red-800 px-4 py-2 text-white transition hover:border-red-600 hover:bg-red-600"
|
||||||
|
v-bind="attrs"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
as: {
|
||||||
|
type: String,
|
||||||
|
default: 'button',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const elementType = computed(() => props.as);
|
||||||
|
|
||||||
|
const attrs = computed(() => {
|
||||||
|
const { as, ...attrs } = props;
|
||||||
|
return attrs;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -20,7 +20,7 @@ const props = defineProps({
|
|||||||
const elementType = computed(() => props.as);
|
const elementType = computed(() => props.as);
|
||||||
|
|
||||||
const attrs = computed(() => {
|
const attrs = computed(() => {
|
||||||
const { as, ...attrs } = props;
|
const { as, ...rest } = props;
|
||||||
return attrs;
|
return rest;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -18,10 +18,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton @click="createClient">{{ $t('client.create') }}</BaseButton>
|
<BasePrimaryButton @click="createClient">
|
||||||
|
{{ $t('client.create') }}
|
||||||
|
</BasePrimaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</template>
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
<BasePrimaryButton>{{ $t('dialog.cancel') }}</BasePrimaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<DialogClose as-child>
|
<DialogClose as-child>
|
||||||
<BaseButton @click="$emit('delete')">{{
|
<BaseSecondaryButton @click="$emit('delete')">
|
||||||
$t('client.deleteClient')
|
{{ $t('client.deleteClient') }}
|
||||||
}}</BaseButton>
|
</BaseSecondaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</template>
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<p class="m-10 text-center text-sm text-gray-400 dark:text-neutral-400">
|
<p class="m-10 text-center text-sm text-gray-400 dark:text-neutral-400">
|
||||||
{{ $t('client.empty') }}<br /><br />
|
{{ $t('client.empty') }}<br /><br />
|
||||||
<ClientsCreateDialog>
|
<ClientsCreateDialog>
|
||||||
<BaseButton as="span">
|
<BaseSecondaryButton as="span">
|
||||||
<IconsPlus class="w-4 md:mr-2" />
|
<IconsPlus class="w-4 md:mr-2" />
|
||||||
<span class="text-sm">{{ $t('client.new') }}</span>
|
<span class="text-sm">{{ $t('client.new') }}</span>
|
||||||
</BaseButton>
|
</BaseSecondaryButton>
|
||||||
</ClientsCreateDialog>
|
</ClientsCreateDialog>
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<ClientsCreateDialog>
|
<ClientsCreateDialog>
|
||||||
<BaseButton as="span">
|
<BaseSecondaryButton as="span">
|
||||||
<IconsPlus class="w-4 md:mr-2" />
|
<IconsPlus class="w-4 md:mr-2" />
|
||||||
<span class="text-sm max-md:hidden">{{ $t('client.newShort') }}</span>
|
<span class="text-sm max-md:hidden">{{ $t('client.newShort') }}</span>
|
||||||
</BaseButton>
|
</BaseSecondaryButton>
|
||||||
</ClientsCreateDialog>
|
</ClientsCreateDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<DialogClose>
|
<DialogClose>
|
||||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</template>
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseButton @click="toggleSort">
|
<BasePrimaryButton @click="toggleSort">
|
||||||
<IconsArrowDown
|
<IconsArrowDown
|
||||||
v-if="globalStore.sortClient === true"
|
v-if="globalStore.sortClient === true"
|
||||||
class="w-4 md:mr-2"
|
class="w-4 md:mr-2"
|
||||||
/>
|
/>
|
||||||
<IconsArrowUp v-else class="w-4 md:mr-2" />
|
<IconsArrowUp v-else class="w-4 md:mr-2" />
|
||||||
<span class="text-sm max-md:hidden"> {{ $t('client.sort') }}</span>
|
<span class="text-sm max-md:hidden"> {{ $t('client.sort') }}</span>
|
||||||
</BaseButton>
|
</BasePrimaryButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
|
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
|
||||||
@input="update($event, i)"
|
@input="update($event, i)"
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseSecondaryButton
|
||||||
as="input"
|
as="input"
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-lg"
|
class="rounded-lg"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<BaseButton
|
<BasePrimaryButton
|
||||||
as="input"
|
as="input"
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-lg"
|
class="rounded-lg"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<IconsInfo class="size-4" />
|
<IconsInfo class="size-4" />
|
||||||
</BaseTooltip>
|
</BaseTooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex gap-1">
|
||||||
<BaseInput
|
<BaseInput
|
||||||
:id="id"
|
:id="id"
|
||||||
v-model.trim="data"
|
v-model.trim="data"
|
||||||
@@ -18,12 +18,12 @@
|
|||||||
/>
|
/>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<AdminSuggestDialog :url="url" @change="data = $event">
|
<AdminSuggestDialog :url="url" @change="data = $event">
|
||||||
<BaseButton as="span">
|
<BasePrimaryButton as="span">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<IconsSparkles class="w-4" />
|
<IconsSparkles class="w-4" />
|
||||||
<span>{{ $t('admin.config.suggest') }}</span>
|
<span>{{ $t('admin.config.suggest') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</BaseButton>
|
</BasePrimaryButton>
|
||||||
</AdminSuggestDialog>
|
</AdminSuggestDialog>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
|
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
|
||||||
@input="update($event, i)"
|
@input="update($event, i)"
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseSecondaryButton
|
||||||
as="input"
|
as="input"
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-lg"
|
class="rounded-lg"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<BaseButton
|
<BasePrimaryButton
|
||||||
as="input"
|
as="input"
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-lg"
|
class="rounded-lg"
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<input
|
||||||
|
:value="label"
|
||||||
|
:type="type ?? 'button'"
|
||||||
|
class="col-span-2 rounded-lg border-2 border-red-800 bg-red-800 py-2 text-white hover:border-red-600 hover:bg-red-600 focus:border-red-800 focus:outline-0 focus:ring-0"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { InputTypeHTMLAttribute } from 'vue';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
label: string;
|
||||||
|
type?: InputTypeHTMLAttribute;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
@@ -15,12 +15,12 @@
|
|||||||
:to="`/admin/${item.id}`"
|
:to="`/admin/${item.id}`"
|
||||||
active-class="bg-red-800 rounded"
|
active-class="bg-red-800 rounded"
|
||||||
>
|
>
|
||||||
<BaseButton
|
<BaseSecondaryButton
|
||||||
as="span"
|
as="span"
|
||||||
class="w-full cursor-pointer rounded p-2 font-medium transition-colors duration-200 hover:bg-red-800 dark:text-neutral-200"
|
class="w-full cursor-pointer rounded p-2 font-medium transition-colors duration-200 hover:bg-red-800 dark:text-neutral-200"
|
||||||
>
|
>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</BaseButton>
|
</BaseSecondaryButton>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -49,8 +49,8 @@
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
||||||
<FormActionField type="submit" :label="$t('form.save')" />
|
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
|
||||||
<FormActionField :label="$t('form.revert')" @click="revert" />
|
<FormSecondaryActionField :label="$t('form.revert')" @click="revert" />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</FormElement>
|
</FormElement>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
||||||
<FormActionField type="submit" :label="$t('form.save')" />
|
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
|
||||||
<FormActionField :label="$t('form.revert')" @click="revert" />
|
<FormSecondaryActionField :label="$t('form.revert')" @click="revert" />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</FormElement>
|
</FormElement>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
||||||
<FormActionField type="submit" :label="$t('form.save')" />
|
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
|
||||||
<FormActionField :label="$t('form.revert')" @click="revert" />
|
<FormSecondaryActionField :label="$t('form.revert')" @click="revert" />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</FormElement>
|
</FormElement>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -23,15 +23,15 @@
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
||||||
<FormActionField type="submit" :label="$t('form.save')" />
|
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
|
||||||
<FormActionField :label="$t('form.revert')" @click="revert" />
|
<FormSecondaryActionField :label="$t('form.revert')" @click="revert" />
|
||||||
<AdminCidrDialog
|
<AdminCidrDialog
|
||||||
trigger-class="col-span-2"
|
trigger-class="col-span-2"
|
||||||
:ipv4-cidr="data.ipv4Cidr"
|
:ipv4-cidr="data.ipv4Cidr"
|
||||||
:ipv6-cidr="data.ipv6Cidr"
|
:ipv6-cidr="data.ipv6Cidr"
|
||||||
@change="changeCidr"
|
@change="changeCidr"
|
||||||
>
|
>
|
||||||
<FormActionField
|
<FormSecondaryActionField
|
||||||
:label="$t('admin.interface.changeCidr')"
|
:label="$t('admin.interface.changeCidr')"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
trigger-class="col-span-2"
|
trigger-class="col-span-2"
|
||||||
@restart="restartInterface"
|
@restart="restartInterface"
|
||||||
>
|
>
|
||||||
<FormActionField
|
<FormSecondaryActionField
|
||||||
:label="$t('admin.interface.restart')"
|
:label="$t('admin.interface.restart')"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
|
|||||||
@@ -107,14 +107,17 @@
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
||||||
<FormActionField type="submit" :label="$t('form.save')" />
|
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
|
||||||
<FormActionField :label="$t('form.revert')" @click="revert" />
|
<FormSecondaryActionField
|
||||||
|
:label="$t('form.revert')"
|
||||||
|
@click="revert"
|
||||||
|
/>
|
||||||
<ClientsDeleteDialog
|
<ClientsDeleteDialog
|
||||||
trigger-class="col-span-2"
|
trigger-class="col-span-2"
|
||||||
:client-name="data.name"
|
:client-name="data.name"
|
||||||
@delete="deleteClient"
|
@delete="deleteClient"
|
||||||
>
|
>
|
||||||
<FormActionField
|
<FormSecondaryActionField
|
||||||
label="Delete"
|
label="Delete"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
v-model="email"
|
v-model="email"
|
||||||
:label="$t('user.email')"
|
:label="$t('user.email')"
|
||||||
/>
|
/>
|
||||||
<FormActionField type="submit" :label="$t('form.save')" />
|
<FormSecondaryActionField type="submit" :label="$t('form.save')" />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</FormElement>
|
</FormElement>
|
||||||
<FormElement @submit.prevent="updatePassword">
|
<FormElement @submit.prevent="updatePassword">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
:label="$t('general.confirmPassword')"
|
:label="$t('general.confirmPassword')"
|
||||||
/>
|
/>
|
||||||
<FormActionField
|
<FormSecondaryActionField
|
||||||
type="submit"
|
type="submit"
|
||||||
:label="$t('general.updatePassword')"
|
:label="$t('general.updatePassword')"
|
||||||
/>
|
/>
|
||||||
@@ -55,7 +55,10 @@
|
|||||||
v-if="!authStore.userData?.totpVerified && !twofa"
|
v-if="!authStore.userData?.totpVerified && !twofa"
|
||||||
class="col-span-2 flex flex-col"
|
class="col-span-2 flex flex-col"
|
||||||
>
|
>
|
||||||
<FormActionField :label="$t('me.enable2fa')" @click="setup2fa" />
|
<FormSecondaryActionField
|
||||||
|
:label="$t('me.enable2fa')"
|
||||||
|
@click="setup2fa"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!authStore.userData?.totpVerified && twofa"
|
v-if="!authStore.userData?.totpVerified && twofa"
|
||||||
@@ -81,7 +84,7 @@
|
|||||||
v-model="code"
|
v-model="code"
|
||||||
:label="$t('general.2faCode')"
|
:label="$t('general.2faCode')"
|
||||||
/>
|
/>
|
||||||
<FormActionField
|
<FormSecondaryActionField
|
||||||
:label="$t('me.enable2fa')"
|
:label="$t('me.enable2fa')"
|
||||||
@click="enable2fa"
|
@click="enable2fa"
|
||||||
/>
|
/>
|
||||||
@@ -101,7 +104,7 @@
|
|||||||
type="password"
|
type="password"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
/>
|
/>
|
||||||
<FormActionField
|
<FormSecondaryActionField
|
||||||
:label="$t('me.disable2fa')"
|
:label="$t('me.disable2fa')"
|
||||||
@click="disable2fa"
|
@click="disable2fa"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
{{ $t('setup.welcomeDesc') }}
|
{{ $t('setup.welcomeDesc') }}
|
||||||
</p>
|
</p>
|
||||||
<NuxtLink to="/setup/2" class="mt-8">
|
<NuxtLink to="/setup/2" class="mt-8">
|
||||||
<BaseButton as="span">{{ $t('general.continue') }}</BaseButton>
|
<BasePrimaryButton as="span">
|
||||||
|
{{ $t('general.continue') }}
|
||||||
|
</BasePrimaryButton>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -29,7 +29,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex justify-center">
|
<div class="mt-4 flex justify-center">
|
||||||
<BaseButton @click="submit">{{ $t('setup.createAccount') }}</BaseButton>
|
<BasePrimaryButton @click="submit">
|
||||||
|
{{ $t('setup.createAccount') }}
|
||||||
|
</BasePrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="mt-4 flex justify-center gap-3">
|
<div class="mt-4 flex justify-center gap-3">
|
||||||
<NuxtLink to="/setup/4" class="w-20">
|
<NuxtLink to="/setup/4" class="w-20">
|
||||||
<BaseButton as="span" class="w-full justify-center">
|
<BasePrimaryButton as="span" class="w-full justify-center">
|
||||||
{{ $t('general.no') }}
|
{{ $t('general.no') }}
|
||||||
</BaseButton>
|
</BasePrimaryButton>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="/setup/migrate" class="w-20">
|
<NuxtLink to="/setup/migrate" class="w-20">
|
||||||
<BaseButton as="span" class="w-full justify-center">
|
<BaseSecondaryButton as="span" class="w-full justify-center">
|
||||||
{{ $t('general.yes') }}
|
{{ $t('general.yes') }}
|
||||||
</BaseButton>
|
</BaseSecondaryButton>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex justify-center">
|
<div class="mt-4 flex justify-center">
|
||||||
<BaseButton @click="submit">{{ $t('general.continue') }}</BaseButton>
|
<BasePrimaryButton @click="submit">
|
||||||
|
{{ $t('general.continue') }}
|
||||||
|
</BasePrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
<input id="migration" type="file" @change="onChangeFile" />
|
<input id="migration" type="file" @change="onChangeFile" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<BaseButton @click="submit">{{ $t('setup.upload') }}</BaseButton>
|
<BasePrimaryButton @click="submit">
|
||||||
|
{{ $t('setup.upload') }}
|
||||||
|
</BasePrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<p>{{ $t('setup.successful') }}</p>
|
<p>{{ $t('setup.successful') }}</p>
|
||||||
<NuxtLink to="/login" class="mt-4">
|
<NuxtLink to="/login" class="mt-4">
|
||||||
<BaseButton as="span">{{ $t('login.signIn') }}</BaseButton>
|
<BasePrimaryButton as="span">{{ $t('login.signIn') }}</BasePrimaryButton>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import esbuild from 'esbuild';
|
||||||
|
|
||||||
|
esbuild.build({
|
||||||
|
entryPoints: [fileURLToPath(new URL('./index.ts', import.meta.url))],
|
||||||
|
bundle: true,
|
||||||
|
outfile: fileURLToPath(new URL('../.output/server/cli.mjs', import.meta.url)),
|
||||||
|
platform: 'node',
|
||||||
|
format: 'esm',
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'make-all-packages-external',
|
||||||
|
setup(build) {
|
||||||
|
let filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../"
|
||||||
|
build.onResolve({ filter }, (args) => ({
|
||||||
|
path: args.path,
|
||||||
|
external: true,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
logLevel: 'info',
|
||||||
|
});
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
node /app/server/cli.mjs "$@"
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// ! Auto Imports are not supported in this file
|
||||||
|
|
||||||
|
import { drizzle } from 'drizzle-orm/libsql';
|
||||||
|
import { createClient } from '@libsql/client';
|
||||||
|
import { defineCommand, runMain } from 'citty';
|
||||||
|
import { consola } from 'consola';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
import packageJson from '../package.json';
|
||||||
|
import * as schema from '../server/database/schema';
|
||||||
|
import { hashPassword } from '../server/utils/password';
|
||||||
|
|
||||||
|
const client = createClient({ url: 'file:/etc/wireguard/wg-easy.db' });
|
||||||
|
const db = drizzle({ client, schema });
|
||||||
|
|
||||||
|
const dbAdminReset = defineCommand({
|
||||||
|
meta: {
|
||||||
|
name: 'db:admin:reset',
|
||||||
|
description: 'Reset the admin user',
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
password: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'New password for the admin user',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async run(ctx) {
|
||||||
|
let password = ctx.args.password || undefined;
|
||||||
|
if (!password) {
|
||||||
|
password = await consola.prompt('Please enter a new password:', {
|
||||||
|
type: 'text',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!password) {
|
||||||
|
consola.error('Password is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password.length < 12) {
|
||||||
|
consola.error('Password must be at least 12 characters long');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.info('Setting new password for admin user...');
|
||||||
|
const hash = await hashPassword(password);
|
||||||
|
|
||||||
|
const user = await db.transaction(async (tx) => {
|
||||||
|
const user = await tx
|
||||||
|
.select()
|
||||||
|
.from(schema.user)
|
||||||
|
.where(eq(schema.user.id, 1))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
consola.error('Admin user not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.update(schema.user)
|
||||||
|
.set({
|
||||||
|
password: hash,
|
||||||
|
})
|
||||||
|
.where(eq(schema.user.id, 1));
|
||||||
|
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
consola.error('Failed to update admin user');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
consola.success(
|
||||||
|
`Successfully updated admin user ${user.id} (${user.username})`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const main = defineCommand({
|
||||||
|
meta: {
|
||||||
|
name: 'wg-easy',
|
||||||
|
version: packageJson.version,
|
||||||
|
description: 'Command Line Interface',
|
||||||
|
},
|
||||||
|
subCommands: {
|
||||||
|
'db:admin:reset': dbAdminReset,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
runMain(main);
|
||||||
@@ -1,9 +1,19 @@
|
|||||||
import en from './locales/en.json';
|
import en from './locales/en.json';
|
||||||
|
import uk from './locales/uk.json';
|
||||||
|
import fr from './locales/fr.json';
|
||||||
|
import de from './locales/de.json';
|
||||||
|
import zhhk from './locales/zh-HK.json';
|
||||||
|
import zhcn from './locales/zh-CN.json';
|
||||||
|
|
||||||
export default defineI18nConfig(() => ({
|
export default defineI18nConfig(() => ({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
messages: {
|
messages: {
|
||||||
en,
|
en,
|
||||||
|
uk,
|
||||||
|
fr,
|
||||||
|
de,
|
||||||
|
'zh-HK': zhhk,
|
||||||
|
'zh-CN': zhcn,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"me": "Konto",
|
||||||
|
"clients": "Clients",
|
||||||
|
"admin": {
|
||||||
|
"panel": "Admin-Konsole",
|
||||||
|
"general": "Allgemein",
|
||||||
|
"config": "Konfiguration",
|
||||||
|
"interface": "Oberfläche",
|
||||||
|
"hooks": "Hooks"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email": "Email"
|
||||||
|
},
|
||||||
|
"me": {
|
||||||
|
"currentPassword": "Aktuelles Passwort",
|
||||||
|
"enable2fa": "Zwei-Faktor-Authentifizierunng aktivieren",
|
||||||
|
"enable2faDesc": "Scannen Sie den QR-Code mit ihrer Authentifizierungs-App oder geben Sie den Schlüssel manuell ein.",
|
||||||
|
"2faKey": "TOTP-Schlüssel",
|
||||||
|
"2faCodeDesc": "Geben Sie den Code aus Ihrer Authentifizierungs-App ein.",
|
||||||
|
"disable2fa": "Zwei-Faktor-Authentifizierung deaktivieren",
|
||||||
|
"disable2faDesc": "Geben Sie Ihr Passwort ein, um die Zwei-Faktor-Authentifizierung zu deaktivieren."
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"name": "Name",
|
||||||
|
"username": "Benutzername",
|
||||||
|
"password": "Passwort",
|
||||||
|
"newPassword": "Neues Passwort",
|
||||||
|
"updatePassword": "Passwort aktualisieren",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"allowedIps": "Erlaubte IP-Adressen",
|
||||||
|
"dns": "DNS",
|
||||||
|
"persistentKeepalive": "Dauerhaftes Keepalive",
|
||||||
|
"logout": "Abmelden",
|
||||||
|
"continue": "Weiter",
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Port",
|
||||||
|
"yes": "Ja",
|
||||||
|
"no": "Nein",
|
||||||
|
"confirmPassword": "Passwort bestätigen",
|
||||||
|
"loading": "Laden...",
|
||||||
|
"2fa": "Zwei-Faktor-Authentifizierung",
|
||||||
|
"2faCode": "TOTP-Code"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"welcome": "Willkommen zur Ersteinrichtung von wg-easy",
|
||||||
|
"welcomeDesc": "Das ist der einfachste Weg, um Wireguard auf jedem Linux-Server zu installieren und zu betreiben.",
|
||||||
|
"existingSetup": "Haben Sie eine bestehende Einrichtung?",
|
||||||
|
"createAdminDesc": "Bitte geben Sie zuerst einen Admin-Benutzernamen sowie ein starkes, sicheres Passwort ein. Diese Anmeldedaten benötigen Sie, um sich im Admin-Panel anzumelden.",
|
||||||
|
"setupConfigDesc": "Bitte geben Sie die Host- und Portinformationen ein. Diese werden für die Client-Konfiguration verwendet, wenn Sie WireGuard auf Ihren Geräten einrichten.",
|
||||||
|
"setupMigrationDesc": "Bitte halten Sie die Sicherungsdatei bereit, wenn Sie Ihre Daten von Ihrer vorherigen wg-easy Version auf ihre neue Einrichtung migrieren möchten.",
|
||||||
|
"upload": "Hochladen",
|
||||||
|
"migration": "Sicherung wiederherstellen:",
|
||||||
|
"createAccount": "Konto erstellen",
|
||||||
|
"successful": "Einrichtung erfolgreich",
|
||||||
|
"hostDesc": "Öffentlicher Hostname mit dem sich die Clients verbinden",
|
||||||
|
"portDesc": "Öffentlicher UDP-Port an dem sich die Clients verbinden und auf dem Wireguard läuft"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"updateAvailable": "Es ist ein neue Aktualisierung verfügbar!",
|
||||||
|
"update": "Aktualisieren"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"dark": "Dunkles Thema",
|
||||||
|
"light": "Helles Thema",
|
||||||
|
"system": "System-Thema"
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"toggleCharts": "Statistiken ein-/ausblenden",
|
||||||
|
"donate": "Spenden"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"signIn": "Anmelden",
|
||||||
|
"rememberMe": "Angemeldet bleiben",
|
||||||
|
"rememberMeDesc": "Bleiben Sie auch nach dem Schließen des Browsers angemeldet",
|
||||||
|
"insecure": "Sie können sich nicht über eine unsichere Verbindung anmelden. Bitte benutzen Sie HTTPS.",
|
||||||
|
"2faRequired": "Zwei-Faktor-Authentifizierung wird benötigt",
|
||||||
|
"2faWrong": "Zwei-Faktor-Authentifizierung ist fehlgeschlagen"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"empty": "Es gibt noch keine Clients.",
|
||||||
|
"newShort": "Neu",
|
||||||
|
"sort": "Sortieren",
|
||||||
|
"create": "Client erstellen",
|
||||||
|
"created": "Client wurde erstellt",
|
||||||
|
"new": "Neuer client",
|
||||||
|
"name": "Name",
|
||||||
|
"expireDate": "Ablaufdatum",
|
||||||
|
"expireDateDesc": "Datum, an dem der Client deaktiviert wird. Leer lassen, damit dies nie passiert.",
|
||||||
|
"deleteClient": "Client löschen",
|
||||||
|
"deleteDialog1": "Sind Sie sicher, dass Sie diesen Client löschen wollen",
|
||||||
|
"deleteDialog2": "Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||||
|
"enabled": "Aktiviert",
|
||||||
|
"address": "Adresse",
|
||||||
|
"serverAllowedIps": "serverseitig erlaubte IP-Adressen",
|
||||||
|
"otlDesc": "Einen kurzen Einmal-Link erzeugen",
|
||||||
|
"permanent": "Dauerhaft",
|
||||||
|
"createdOn": "Angelegt am ",
|
||||||
|
"lastSeen": "Zuletzt verbunden am ",
|
||||||
|
"totalDownload": "Gesamt-Download: ",
|
||||||
|
"totalUpload": "Gesamt-Upload: ",
|
||||||
|
"newClient": "Neuer Client",
|
||||||
|
"disableClient": "Client deaktivieren",
|
||||||
|
"enableClient": "Client aktivieren",
|
||||||
|
"noPrivKey": "Dieser Client hat keinen bekannten privaten Schlüssel, weshalb keine Konfiguration angelegt werden kann.",
|
||||||
|
"showQR": "QR-Code anzeigen",
|
||||||
|
"downloadConfig": "Konfiguration herunterladen",
|
||||||
|
"allowedIpsDesc": "Welche IP-Adressen durch das VPN geleitet werden (überschreibt die globale Konfiguration)",
|
||||||
|
"serverAllowedIpsDesc": "Welche IP-Adressen der Server zum Client leiten wird",
|
||||||
|
"mtuDesc": "Setzt die maximale Übertragungsgröße (Paketgröße) für den VPN-Tunnel",
|
||||||
|
"persistentKeepaliveDesc": "Legt das Intervall (in Sekunden) für Keepalive-Pakete fest. 0 deaktiviert es",
|
||||||
|
"hooks": "Hooks",
|
||||||
|
"hooksDescription": "Hooks funktionieren nur mit wg-quick",
|
||||||
|
"hooksLeaveEmpty": "Nur für wg-quick. Sonst leer lassen",
|
||||||
|
"dnsDesc": "DNS-Server, den die Clients benutzen (überschreibt die globale Konfiguration)"
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"change": "Ändern",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"create": "Erstellen"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"success": "Erfolg",
|
||||||
|
"saved": "Gespeichert",
|
||||||
|
"error": "Fehler"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"actions": "Aktionen",
|
||||||
|
"save": "Speichern",
|
||||||
|
"revert": "Rückgängig machen",
|
||||||
|
"sectionGeneral": "Allgemein",
|
||||||
|
"sectionAdvanced": "Erweitert",
|
||||||
|
"noItems": "Keine Einträge",
|
||||||
|
"nullNoItems": "Keine Einträge. Die globale Konfiguration wird benutzt",
|
||||||
|
"add": "Hinzufügen"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"general": {
|
||||||
|
"sessionTimeout": "Sitzungszeitüberschreitung",
|
||||||
|
"sessionTimeoutDesc": "Sitzungsdauer für \"Angemeldet bleiben\" (Sekunden)",
|
||||||
|
"metrics": "Statistiken",
|
||||||
|
"metricsPassword": "Passwort",
|
||||||
|
"metricsPasswordDesc": "Bearer-Passwort für den Statistik-Endpunkt (Passwort oder Argon2-Hash)",
|
||||||
|
"json": "JSON",
|
||||||
|
"jsonDesc": "Pfad zu den Statistiken als JSON",
|
||||||
|
"prometheus": "Prometheus",
|
||||||
|
"prometheusDesc": "Pfad zu den Prometheus-Statistiken"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"connection": "Verbindung",
|
||||||
|
"hostDesc": "Öffentlicher Hostname mit dem sich die Clients verbinden (überschreibt die Konfiguration)",
|
||||||
|
"portDesc": "Öffentlicher UDP-Port an dem sich die Clients verbinden (überschreibt die Konfiguration, vermutlich wollen Sie ebenfalls den Port der Weboberfläche ändern)",
|
||||||
|
"allowedIpsDesc": "Erlaubte IP-Adressen, die die Clients nutzen werden (Globale Konfiguration)",
|
||||||
|
"dnsDesc": "DNS-Server, den die Clients nutzen werden (Globale Konfiguration)",
|
||||||
|
"mtuDesc": "MTU, den die Clients benutzen werden (nur für neue Clients)",
|
||||||
|
"persistentKeepaliveDesc": "Intervall in Sekunden, in dem Keepalive-Packete an den Server gesendet werden. 0 = deaktiviert (nur für neue Clients)",
|
||||||
|
"suggest": "Vorschlagen",
|
||||||
|
"suggestDesc": "Wählen Sie eine IP-Adresse oder einen Hostnamen für das Host-Feld aus"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"cidrSuccess": "CIDR wurde geändert",
|
||||||
|
"device": "Gerät",
|
||||||
|
"deviceDesc": "Ethernet-Gerät, durch das der Wireguard-Datenverkehr geleitet werden soll",
|
||||||
|
"mtuDesc": "MTU, den WireGuard benutzen wird",
|
||||||
|
"portDesc": "UDP Port, auf dem WireGuard lauschen wird (Sie wollen wahrscheinlich auch den Config-Port ändern)",
|
||||||
|
"changeCidr": "CIDR ändern",
|
||||||
|
"restart": "Interface neu starten",
|
||||||
|
"restartDesc": "Das WireGuard-Interface neu starten",
|
||||||
|
"restartWarn": "Sind Sie sicher, dass Sie das Interface neu starten wollen? Dies wird die Verbindungen aller Clients trennen.",
|
||||||
|
"restartSuccess": "Interface neu gestartet"
|
||||||
|
},
|
||||||
|
"introText": "Willkommen in der Admin-Konsole.\n\nHier können Sie die allgemeinen Einstellungen, die Konfiguration, die Schnittstelleneinstellungen und die Hooks verwalten.\n\nBeginnen Sie, indem Sie einen der Bereiche in der Seitenleiste auswählen."
|
||||||
|
},
|
||||||
|
"zod": {
|
||||||
|
"generic": {
|
||||||
|
"required": "{0} ist erforderlich",
|
||||||
|
"validNumber": "{0} muss eine Zahl sein",
|
||||||
|
"validString": "{0} muss eine Zeichenkette sein",
|
||||||
|
"validBoolean": "{0} muss ein Wahrheitswert sein",
|
||||||
|
"validArray": "{0} muss eine Liste sein",
|
||||||
|
"stringMin": "{0} muss mindestens {1} Zeichen lang sein",
|
||||||
|
"numberMin": "{0} muss mindestens {1} sein"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"id": "Client-ID",
|
||||||
|
"name": "Name",
|
||||||
|
"expiresAt": "Läuft ab am",
|
||||||
|
"address4": "IPv4-Adresse",
|
||||||
|
"address6": "IPv6-Adresse",
|
||||||
|
"serverAllowedIps": "serverseitig erlaubte IP-Adressen"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"username": "Benutzername",
|
||||||
|
"password": "Passwort",
|
||||||
|
"remember": "Merken",
|
||||||
|
"name": "Name",
|
||||||
|
"email": "Email",
|
||||||
|
"emailInvalid": "Die Email-Adresse muss valide sein",
|
||||||
|
"passwordMatch": "Die Passwörter müssen übereinstimmen",
|
||||||
|
"totpEnable": "TOTP aktivieren",
|
||||||
|
"totpEnableTrue": "\"TOTP aktivieren\" muss ausgewählt sein",
|
||||||
|
"totpCode": "TOTP-Code"
|
||||||
|
},
|
||||||
|
"userConfig": {
|
||||||
|
"host": "Host"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"sessionTimeout": "Sitzungszeitüberschreitung",
|
||||||
|
"metricsEnabled": "Statistiken",
|
||||||
|
"metricsPassword": "Passwort für Statistiken"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"cidr": "CIDR",
|
||||||
|
"device": "Geräte",
|
||||||
|
"cidrValid": "CIDR muss valide sein"
|
||||||
|
},
|
||||||
|
"otl": "Einmal-Link",
|
||||||
|
"stringMalformed": "Zeichenkette ist fehlerhaft",
|
||||||
|
"body": "Body muss ein gültiges Objekt sein",
|
||||||
|
"hook": "Hook",
|
||||||
|
"enabled": "Aktiviert",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"port": "Port",
|
||||||
|
"persistentKeepalive": "Dauerhaftes Keepalive",
|
||||||
|
"address": "IP-Adresse",
|
||||||
|
"dns": "DNS",
|
||||||
|
"allowedIps": "Erlaubte IP-Adressen",
|
||||||
|
"file": "Datei"
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"preUp": "PreUp",
|
||||||
|
"postUp": "PostUp",
|
||||||
|
"preDown": "PreDown",
|
||||||
|
"postDown": "PostDown"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"me": "Compte",
|
||||||
|
"clients": "Clients",
|
||||||
|
"admin": {
|
||||||
|
"panel": "Panel Admin",
|
||||||
|
"general": "Général",
|
||||||
|
"config": "Config",
|
||||||
|
"interface": "Interface",
|
||||||
|
"hooks": "Hooks"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email": "E-Mail"
|
||||||
|
},
|
||||||
|
"me": {
|
||||||
|
"currentPassword": "Mot de passe actuel",
|
||||||
|
"enable2fa": "Activer l'authentification à double facteur",
|
||||||
|
"enable2faDesc": "Scannez le code QR avec votre application d'authentification ou saisissez la clé manuellement.",
|
||||||
|
"2faKey": "Clé TOTP",
|
||||||
|
"2faCodeDesc": "Saisissez le code de votre application d'authentification.",
|
||||||
|
"disable2fa": "Désactiver l'authentification à double facteur",
|
||||||
|
"disable2faDesc": "Saisissez votre mot de passe pour désactiver l'authentification à double facteur"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"name": "Nom",
|
||||||
|
"username": "Nom d'utilisateur",
|
||||||
|
"password": "Mot de passe",
|
||||||
|
"newPassword": "Nouveau mot de passe",
|
||||||
|
"updatePassword": "Mettre à jour le mot de passe",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"allowedIps": "IPs autorisées",
|
||||||
|
"dns": "DNS",
|
||||||
|
"persistentKeepalive": "Keepalive persistant",
|
||||||
|
"logout": "Déconnexion",
|
||||||
|
"continue": "Continuer",
|
||||||
|
"host": "Hôte",
|
||||||
|
"port": "Port",
|
||||||
|
"yes": "Oui",
|
||||||
|
"no": "Non",
|
||||||
|
"confirmPassword": "Confirmer le mot de passe",
|
||||||
|
"loading": "Chargement...",
|
||||||
|
"2fa": "Authentification à double facteur",
|
||||||
|
"2faCode": "Code TOTP"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"welcome": "Bienvenue dans votre première installation de wg-easy",
|
||||||
|
"welcomeDesc": "Vous avez trouvé la façon la plus simple d'installer et de gérer WireGuard sur n'importe quel hôte Linux.",
|
||||||
|
"existingSetup": "Avez-vous une installation existante ?",
|
||||||
|
"createAdminDesc": "Veuillez d'abord saisir un nom d'utilisateur administrateur et un mot de passe sécurisé. Ces informations seront utilisées pour vous connecter à votre panel d'administration.",
|
||||||
|
"setupConfigDesc": "Veuillez saisir les informations relatives à l'hôte et au port. Ceci sera utilisé pour la configuration du client lors de la mise en place de WireGuard sur les appareils.",
|
||||||
|
"setupMigrationDesc": "Veuillez fournir le fichier de sauvegarde si vous souhaitez migrer vos données de la version précédente de wg-easy vers votre nouvelle installation.",
|
||||||
|
"upload": "Téléverser",
|
||||||
|
"migration": "Restaurer la sauvegarde:",
|
||||||
|
"createAccount": "Créer un compte",
|
||||||
|
"successful": "Installation réussie",
|
||||||
|
"hostDesc": "Nom d'hôte public auquel les clients se connecteront",
|
||||||
|
"portDesc": "Port UDP public auquel les clients se connecteront et sur lequel WireGuard écoutera"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"updateAvailable": "Une mise à jour est disponible!",
|
||||||
|
"update": "Mise à jour"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"dark": "Thème sombre",
|
||||||
|
"light": "Thème clair",
|
||||||
|
"system": "Thème système"
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"toggleCharts": "Afficher/masquer les graphiques",
|
||||||
|
"donate": "Donation"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"signIn": "Se connecter",
|
||||||
|
"rememberMe": "Se souvenir de moi",
|
||||||
|
"rememberMeDesc": "Rester connecté après avoir fermé le navigateur",
|
||||||
|
"insecure": "Vous ne pouvez pas vous connecter avec une connexion non sécurisée. Utilisez HTTPS.",
|
||||||
|
"2faRequired": "Une authentification à double facteur est requise",
|
||||||
|
"2faWrong": "L'authentification à double facteur est incorrecte"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"empty": "Il n'y a pas encore de clients.",
|
||||||
|
"newShort": "Nouveau",
|
||||||
|
"sort": "Trier",
|
||||||
|
"create": "Créer un client",
|
||||||
|
"created": "Client créé",
|
||||||
|
"new": "Nouveau client",
|
||||||
|
"name": "Nom",
|
||||||
|
"expireDate": "Date d'expiration",
|
||||||
|
"expireDateDesc": "Date à laquelle le client sera désactivé. Vide pour permanent",
|
||||||
|
"deleteClient": "Supprimer le client",
|
||||||
|
"deleteDialog1": "Êtes-vous sûr de vouloir supprimer",
|
||||||
|
"deleteDialog2": "Cette action ne peut être annulée.",
|
||||||
|
"enabled": "Activé",
|
||||||
|
"address": "Adresse",
|
||||||
|
"serverAllowedIps": "Serveur IPs autorisées",
|
||||||
|
"otlDesc": "Générer un lien court et unique",
|
||||||
|
"permanent": "Permanent",
|
||||||
|
"createdOn": "Créé le ",
|
||||||
|
"lastSeen": "Dernière visite le ",
|
||||||
|
"totalDownload": "Téléchargement total: ",
|
||||||
|
"totalUpload": "Téléversement total: ",
|
||||||
|
"newClient": "Nouveau client",
|
||||||
|
"disableClient": "Désactiver le client",
|
||||||
|
"enableClient": "Activer le client",
|
||||||
|
"noPrivKey": "Ce client n'a pas de clé privée connue. Impossible de créer une configuration.",
|
||||||
|
"showQR": "Afficher le code QR",
|
||||||
|
"downloadConfig": "Télécharger la configuration",
|
||||||
|
"allowedIpsDesc": "Quelles IPs seront acheminées par le VPN (remplace la configuration globale)",
|
||||||
|
"serverAllowedIpsDesc": "Les IPs que le serveur acheminera vers le client",
|
||||||
|
"mtuDesc": "Définit le nombre maximum d'unités de transmission (taille des paquets) pour le tunnel VPN.",
|
||||||
|
"persistentKeepaliveDesc": "Définit l'intervalle (en secondes) pour les paquets keep-alive. 0 le désactive",
|
||||||
|
"hooks": "Hooks",
|
||||||
|
"hooksDescription": "Les hooks ne fonctionnent qu'avec wg-quick",
|
||||||
|
"hooksLeaveEmpty": "Uniquement pour wg-quick. Sinon, laissez-le vide",
|
||||||
|
"dnsDesc": "Serveur DNS que les clients utiliseront (remplace la configuration globale)"
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"change": "Modifier",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"create": "Créer"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"success": "Réussite",
|
||||||
|
"saved": "Sauvegardé",
|
||||||
|
"error": "Erreur"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"actions": "Actions",
|
||||||
|
"save": "Sauvegarder",
|
||||||
|
"revert": "Revenir en arrière",
|
||||||
|
"sectionGeneral": "Général",
|
||||||
|
"sectionAdvanced": "Avancé",
|
||||||
|
"noItems": "Aucun élément",
|
||||||
|
"nullNoItems": "Aucun élément. Utilisation de la configuration globale",
|
||||||
|
"add": "Ajouter"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"general": {
|
||||||
|
"sessionTimeout": "Délai d'expiration de la session",
|
||||||
|
"sessionTimeoutDesc": "Durée de la session pour Se souvenir de moi (secondes)",
|
||||||
|
"metrics": "Métriques",
|
||||||
|
"metricsPassword": "Mot de passe",
|
||||||
|
"metricsPasswordDesc": "Mot de passe Bearer pour le endpoint des métriques (mot de passe ou argon2 hash)",
|
||||||
|
"json": "JSON",
|
||||||
|
"jsonDesc": "Acheminement pour les métriques au format JSON",
|
||||||
|
"prometheus": "Prometheus",
|
||||||
|
"prometheusDesc": "Acheminement pour les métriques de Prometheus"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"connection": "Connexion",
|
||||||
|
"hostDesc": "Nom d'hôte public auquel les clients se connecteront (invalide la configuration)",
|
||||||
|
"portDesc": "Port UDP public auquel les clients se connecteront (invalide la configuration, vous souhaiterez probablement modifier le port d'interface également)",
|
||||||
|
"allowedIpsDesc": "IPs autorisées que les clients utiliseront (configuration globale)",
|
||||||
|
"dnsDesc": "Serveur DNS que les clients utiliseront (configuration globale)",
|
||||||
|
"mtuDesc": "MTU que les clients utiliseront (uniquement pour les nouveaux clients)",
|
||||||
|
"persistentKeepaliveDesc": "Intervalle en secondes keepalives du serveur. 0 = désactivé (uniquement pour les nouveaux clients)",
|
||||||
|
"suggest": "Suggérer",
|
||||||
|
"suggestDesc": "Choisissez une adresse IP ou un nom d'hôte pour le champ Hôte."
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"cidrSuccess": "CIDR modifié",
|
||||||
|
"device": "Périphérique",
|
||||||
|
"deviceDesc": "Périphérique Ethernet dans lequel le trafic wireguard sera transmis.",
|
||||||
|
"mtuDesc": "MTU que WireGuard utilisera",
|
||||||
|
"portDesc": "Port UDP sur lequel WireGuard écoutera (vous souhaiterez probablement changer le port de configuration aussi)",
|
||||||
|
"changeCidr": "Modifier CIDR",
|
||||||
|
"restart": "Redémarrer l'interface",
|
||||||
|
"restartDesc": "Redémarre l'interface WireGuard",
|
||||||
|
"restartWarn": "Êtes-vous sûr de redémarrer l'interface ? Cela déconnectera tous les clients.",
|
||||||
|
"restartSuccess": "Interface redémarrée"
|
||||||
|
},
|
||||||
|
"introText": "Bienvenue dans le panel d'administration.\n\nVous pouvez y gérer les paramètres généraux, la configuration, les paramètres de l'interface et les hooks.\n\nCommencez par choisir l'une des sections de la barre latérale."
|
||||||
|
},
|
||||||
|
"zod": {
|
||||||
|
"generic": {
|
||||||
|
"required": "{0} est requis",
|
||||||
|
"validNumber": "{0} doit être un nombre valide",
|
||||||
|
"validString": "{0} doit être une chaîne de caractères valide",
|
||||||
|
"validBoolean": "{0} doit être une variable valide",
|
||||||
|
"validArray": "{0} doit être un tableau valide",
|
||||||
|
"stringMin": "{0} doit être d'au moins {1} Caractère",
|
||||||
|
"numberMin": "{0} doit être d'au moins {1}"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"id": "Client ID",
|
||||||
|
"name": "Nom",
|
||||||
|
"expiresAt": "Expire le",
|
||||||
|
"address4": "Adresse IPv4",
|
||||||
|
"address6": "Adresse IPv6",
|
||||||
|
"serverAllowedIps": "Serveur IPs autorisées"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"username": "Nom d'utilisateur",
|
||||||
|
"password": "Mot de passe",
|
||||||
|
"remember": "Se souvenir",
|
||||||
|
"name": "Nom",
|
||||||
|
"email": "Email",
|
||||||
|
"emailInvalid": "L'email doit être valide",
|
||||||
|
"passwordMatch": "Les mots de passe doivent correspondre",
|
||||||
|
"totpEnable": "Activer TOTP",
|
||||||
|
"totpEnableTrue": "Activer TOTP doit être activé",
|
||||||
|
"totpCode": "Code TOTP"
|
||||||
|
},
|
||||||
|
"userConfig": {
|
||||||
|
"host": "Hôte"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"sessionTimeout": "Délai d'expiration de la session",
|
||||||
|
"metricsEnabled": "Métriques",
|
||||||
|
"metricsPassword": "Mot de passe métriques"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"cidr": "CIDR",
|
||||||
|
"device": "Périphérique",
|
||||||
|
"cidrValid": "CIDR doit être valide"
|
||||||
|
},
|
||||||
|
"otl": "Lien unique",
|
||||||
|
"stringMalformed": "La chaîne est malformée",
|
||||||
|
"body": "Le corps doit être un objet valide",
|
||||||
|
"hook": "Hook",
|
||||||
|
"enabled": "Activé",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"port": "Port",
|
||||||
|
"persistentKeepalive": "Keepalive persistant",
|
||||||
|
"address": "Adresse IP",
|
||||||
|
"dns": "DNS",
|
||||||
|
"allowedIps": "IPs autorisées",
|
||||||
|
"file": "Fichier"
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"preUp": "PreUp",
|
||||||
|
"postUp": "PostUp",
|
||||||
|
"preDown": "PreDown",
|
||||||
|
"postDown": "PostDown"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"me": "Обліковий запис",
|
||||||
|
"clients": "Клієнти",
|
||||||
|
"admin": {
|
||||||
|
"panel": "Панель адміністратора",
|
||||||
|
"general": "Загальні налаштування",
|
||||||
|
"config": "Конфігурація",
|
||||||
|
"interface": "Інтерфейс",
|
||||||
|
"hooks": "Hooks"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email": "Електронна пошта"
|
||||||
|
},
|
||||||
|
"me": {
|
||||||
|
"currentPassword": "Поточний пароль",
|
||||||
|
"enable2fa": "Увімкнути двофакторну автентифікацію",
|
||||||
|
"enable2faDesc": "Відскануйте QR-код за допомогою програми автентифікації або введіть ключ вручну.",
|
||||||
|
"2faKey": "TOTP Ключ",
|
||||||
|
"2faCodeDesc": "Введіть код з вашої програми автентифікатора.",
|
||||||
|
"disable2fa": "Вимкнути двофакторну автентифікацію",
|
||||||
|
"disable2faDesc": "Введіть свій пароль, щоб вимкнути двофакторну автентифікацію."
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"name": "Ім'я",
|
||||||
|
"username": "Ім'я користувача",
|
||||||
|
"password": "Пароль",
|
||||||
|
"newPassword": "Новий пароль",
|
||||||
|
"updatePassword": "Оновити пароль",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"allowedIps": "Дозволені IP",
|
||||||
|
"dns": "DNS",
|
||||||
|
"persistentKeepalive": "Постійна підтримка активності",
|
||||||
|
"logout": "Вийти",
|
||||||
|
"continue": "Продовжити",
|
||||||
|
"host": "Хост",
|
||||||
|
"port": "Порт",
|
||||||
|
"yes": "Так",
|
||||||
|
"no": "Ні",
|
||||||
|
"confirmPassword": "Підтвердити пароль",
|
||||||
|
"loading": "Завантаження...",
|
||||||
|
"2fa": "Двофакторна автентифікація",
|
||||||
|
"2faCode": "TOTP код"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"welcome": "Ласкаво просимо до вашого першого налаштування wg-easy",
|
||||||
|
"welcomeDesc": "Ви знайшли найпростіший спосіб встановити та керувати 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": "Встановлює інтервал (у секундах) для пакетів keep-alive. 0 вимикає його",
|
||||||
|
"hooks": "Hooks",
|
||||||
|
"hooksDescription": "Hooks працюють лише з wg-quick",
|
||||||
|
"hooksLeaveEmpty": "Тільки для wg-quick. Інакше залиште порожнім",
|
||||||
|
"dnsDesc": "DNS сервер, який використовуватимуть клієнти (перевизначає глобальну конфігурацію)"
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"change": "Змінити",
|
||||||
|
"cancel": "Скасувати",
|
||||||
|
"create": "Створити"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"success": "Успіх",
|
||||||
|
"saved": "Збережено",
|
||||||
|
"error": "Помилка"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"actions": "Дії",
|
||||||
|
"save": "Зберегти",
|
||||||
|
"revert": "Повернути",
|
||||||
|
"sectionGeneral": "Загальні",
|
||||||
|
"sectionAdvanced": "Додатково",
|
||||||
|
"noItems": "Немає елементів",
|
||||||
|
"nullNoItems": "Немає елементів. Використовується глобальна конфігурація",
|
||||||
|
"add": "Додати"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"general": {
|
||||||
|
"sessionTimeout": "Час очікування сеансу",
|
||||||
|
"sessionTimeoutDesc": "Тривалість сеансу для функції 'Запам'ятати мене' (секунди)",
|
||||||
|
"metrics": "Метрики",
|
||||||
|
"metricsPassword": "Пароль",
|
||||||
|
"metricsPasswordDesc": "Пароль Bearer для точки доступу метрик (пароль або хеш argon2)",
|
||||||
|
"json": "JSON",
|
||||||
|
"jsonDesc": "Маршрут для метрик у форматі JSON",
|
||||||
|
"prometheus": "Prometheus",
|
||||||
|
"prometheusDesc": "Маршрут для метрики Prometheus"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"connection": "З'єднання",
|
||||||
|
"hostDesc": "Публічне ім'я хоста для підключення клієнтів (спрацьовує при зміні конфігурації)",
|
||||||
|
"portDesc": "Публічний UDP порт для підключення клієнтів (спрацьовує при зміні конфігурації, можливо, варто змінити порт інтерфейсу теж)",
|
||||||
|
"allowedIpsDesc": "Дозволені IP-адреси, які використовуватимуть клієнти (глобальна конфігурація)",
|
||||||
|
"dnsDesc": "DNS-сервера, який використовуватимуть клієнти (глобальну конфігурацію)",
|
||||||
|
"mtuDesc": "MTU, який використовуватимуть клієнти (лише для нових клієнтів)",
|
||||||
|
"persistentKeepaliveDesc": "Інтервал у секундах для надсилання keepalive на сервер. 0 = вимкнено (лише для нових клієнтів)",
|
||||||
|
"suggest": "Запропонувати",
|
||||||
|
"suggestDesc": "Виберіть IP-адресу або ім'я хоста для поля 'Хост'"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"cidrSuccess": "CIDR змінено",
|
||||||
|
"device": "Пристрій",
|
||||||
|
"deviceDesc": "Ethernet-пристрій, через який має проходити трафік 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": "Ідентифікатор клієнта",
|
||||||
|
"name": "Ім'я",
|
||||||
|
"expiresAt": "Термін дії закінчується о",
|
||||||
|
"address4": "IPv4-адреса",
|
||||||
|
"address6": "IPv6-адреса",
|
||||||
|
"serverAllowedIps": "Дозволені IP-адреси сервера"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"username": "Ім'я користувача",
|
||||||
|
"password": "Пароль",
|
||||||
|
"remember": "Пам'ятати",
|
||||||
|
"name": "Ім'я",
|
||||||
|
"email": "Електронна пошта",
|
||||||
|
"emailInvalid": "Email має бути дійсною",
|
||||||
|
"passwordMatch": "Паролі мають збігатися",
|
||||||
|
"totpEnable": "Увімкнути TOTP",
|
||||||
|
"totpEnableTrue": "Увімкнення TOTP має бути true",
|
||||||
|
"totpCode": "TOTP Код"
|
||||||
|
},
|
||||||
|
"userConfig": {
|
||||||
|
"host": "Хост"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"sessionTimeout": "Час очікування сеансу",
|
||||||
|
"metricsEnabled": "Метрики",
|
||||||
|
"metricsPassword": "Пароль метрик"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"cidr": "CIDR",
|
||||||
|
"device": "Пристрій",
|
||||||
|
"cidrValid": "CIDR має бути дійсним"
|
||||||
|
},
|
||||||
|
"otl": "Одноразове посилання",
|
||||||
|
"stringMalformed": "Рядок має неправильний формат",
|
||||||
|
"body": "Тіло має бути коректним об'єктом",
|
||||||
|
"hook": "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,236 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"me": "账户管理",
|
||||||
|
"clients": "客户端管理",
|
||||||
|
"admin": {
|
||||||
|
"panel": "管理面板",
|
||||||
|
"general": "通用设置",
|
||||||
|
"config": "网络配置",
|
||||||
|
"interface": "接口配置",
|
||||||
|
"hooks": "钩子脚本"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email": "电子邮箱"
|
||||||
|
},
|
||||||
|
"me": {
|
||||||
|
"currentPassword": "当前密码",
|
||||||
|
"enable2fa": "启用双重认证",
|
||||||
|
"enable2faDesc": "使用认证器应用扫描二维码或手动输入密钥进行配置",
|
||||||
|
"2faKey": "TOTP密钥",
|
||||||
|
"2faCodeDesc": "请输入您的认证器应用生成的验证码",
|
||||||
|
"disable2fa": "禁用双重认证",
|
||||||
|
"disable2faDesc": "您需要输入当前密码来禁用双重认证功能"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"name": "名称",
|
||||||
|
"username": "用户名",
|
||||||
|
"password": "密码",
|
||||||
|
"newPassword": "新密码",
|
||||||
|
"updatePassword": "更新密码",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"allowedIps": "允许的IP",
|
||||||
|
"dns": "DNS",
|
||||||
|
"persistentKeepalive": "保活间隔",
|
||||||
|
"logout": "注销登录",
|
||||||
|
"continue": "继续",
|
||||||
|
"host": "主机",
|
||||||
|
"port": "端口",
|
||||||
|
"yes": "是",
|
||||||
|
"no": "否",
|
||||||
|
"confirmPassword": "确认密码",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"2fa": "双重认证",
|
||||||
|
"2faCode": "验证码"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"welcome": "欢迎使用wg-easy安装向导",
|
||||||
|
"welcomeDesc": "您正在使用最简单的WireGuard Linux主机安装和管理方案",
|
||||||
|
"existingSetup": "是否已有现有配置?",
|
||||||
|
"createAdminDesc": "请首先输入管理员用户名和强密码。这些信息将用于登录管理面板。",
|
||||||
|
"setupConfigDesc": "请输入服务器主机和端口信息。这些设置将用于客户端设备连接WireGuard时的配置。",
|
||||||
|
"setupMigrationDesc": "如果您希望从旧版wg-easy迁移数据到新安装,请提供备份文件。",
|
||||||
|
"upload": "上传文件",
|
||||||
|
"migration": "从备份恢复配置:",
|
||||||
|
"createAccount": "创建账户",
|
||||||
|
"successful": "安装配置成功",
|
||||||
|
"hostDesc": "客户端将连接到的公共主机名或IP地址",
|
||||||
|
"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": "IP地址",
|
||||||
|
"serverAllowedIps": "服务端允许的IP",
|
||||||
|
"otlDesc": "生成一次性使用的短链接配置",
|
||||||
|
"permanent": "永久有效",
|
||||||
|
"createdOn": "创建于 ",
|
||||||
|
"lastSeen": "最后在线时间 ",
|
||||||
|
"totalDownload": "总下载流量: ",
|
||||||
|
"totalUpload": "总上传流量: ",
|
||||||
|
"newClient": "新建客户端",
|
||||||
|
"disableClient": "禁用客户端",
|
||||||
|
"enableClient": "启用客户端",
|
||||||
|
"noPrivKey": "此客户端没有已知的私钥,无法创建配置文件",
|
||||||
|
"showQR": "显示二维码",
|
||||||
|
"downloadConfig": "下载配置文件",
|
||||||
|
"allowedIpsDesc": "指定将通过VPN路由的IP地址(覆盖全局配置)",
|
||||||
|
"serverAllowedIpsDesc": "指定服务端将路由到客户端的IP地址范围",
|
||||||
|
"mtuDesc": "设置VPN隧道的MTU(最大传输单元)",
|
||||||
|
"persistentKeepaliveDesc": "设置保活数据包的发送间隔(秒)。0表示禁用",
|
||||||
|
"hooks": "钩子脚本",
|
||||||
|
"hooksDescription": "钩子脚本仅在使用wg-quick时有效",
|
||||||
|
"hooksLeaveEmpty": "如果不使用wg-quick,请留空此字段"
|
||||||
|
},
|
||||||
|
"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": "向服务器发送保活数据包的间隔秒数。0表示禁用(仅影响新客户端)",
|
||||||
|
"suggest": "自动检测",
|
||||||
|
"suggestDesc": "为'主机'字段选择IP地址或主机名"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"cidrSuccess": "CIDR修改成功",
|
||||||
|
"device": "网络设备",
|
||||||
|
"deviceDesc": "用于转发WireGuard流量的以太网设备",
|
||||||
|
"mtuDesc": "WireGuard接口使用的MTU",
|
||||||
|
"portDesc": "WireGuard监听的UDP端口(通常需要同时修改配置端口)",
|
||||||
|
"changeCidr": "修改CIDR",
|
||||||
|
"restart": "重启接口",
|
||||||
|
"restartDesc": "重新启动WireGuard接口",
|
||||||
|
"restartWarn": "确定要重启接口吗?这将断开所有客户端的连接。",
|
||||||
|
"restartSuccess": "接口重启成功"
|
||||||
|
},
|
||||||
|
"introText": "欢迎使用管理控制台。\n\n您可以在这里管理通用设置、网络配置、接口设置和钩子脚本。\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": "必须启用双重认证",
|
||||||
|
"totpCode": "验证码"
|
||||||
|
},
|
||||||
|
"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": "启动前脚本",
|
||||||
|
"postUp": "启动后脚本",
|
||||||
|
"preDown": "停止前脚本",
|
||||||
|
"postDown": "停止后脚本"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"me": "帳戶",
|
||||||
|
"clients": "客戶端",
|
||||||
|
"admin": {
|
||||||
|
"panel": "管理員版面",
|
||||||
|
"general": "一般設定",
|
||||||
|
"config": "配置設定",
|
||||||
|
"interface": "介面設定",
|
||||||
|
"hooks": "掛鉤設定"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"email": "電郵"
|
||||||
|
},
|
||||||
|
"me": {
|
||||||
|
"currentPassword": "目前密碼",
|
||||||
|
"enable2fa": "啟用雙重認證",
|
||||||
|
"enable2faDesc": "使用認證應用程式掃描二維碼,或手動輸入密匙。",
|
||||||
|
"2faKey": "TOTP密匙",
|
||||||
|
"2faCodeDesc": "請輸入認證應用程式中的驗證碼。",
|
||||||
|
"disable2fa": "停用雙重認證",
|
||||||
|
"disable2faDesc": "請輸入密碼以停用雙重認證。"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"name": "名稱",
|
||||||
|
"username": "用戶名",
|
||||||
|
"password": "密碼",
|
||||||
|
"newPassword": "新密碼",
|
||||||
|
"updatePassword": "更改密碼",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"allowedIps": "IP白名單",
|
||||||
|
"dns": "域名系統",
|
||||||
|
"persistentKeepalive": "保持連線",
|
||||||
|
"logout": "登出",
|
||||||
|
"continue": "繼續",
|
||||||
|
"host": "主機",
|
||||||
|
"port": "連接埠",
|
||||||
|
"yes": "是",
|
||||||
|
"no": "否",
|
||||||
|
"confirmPassword": "確認新密碼",
|
||||||
|
"loading": "載入中...",
|
||||||
|
"2fa": "雙重認證",
|
||||||
|
"2faCode": "TOTP驗證碼"
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"welcome": "歡迎首次設定wg-easy",
|
||||||
|
"welcomeDesc": "這是最簡單的方法讓你在任何Linux主機上安裝及管理WireGuard",
|
||||||
|
"existingSetup": "你有現存設定嗎?",
|
||||||
|
"createAdminDesc": "請輸入管理員用戶名及一個高強度密碼。這些資料將用於登入管理員版面。",
|
||||||
|
"setupConfigDesc": "請輸入主機及連接埠資料。這將用於客戶端設定,以便在他們的裝置上設定WireGuard。",
|
||||||
|
"setupMigrationDesc": "如果你想將舊版wg-easy的資料轉移到新設定,請提供備份檔案。",
|
||||||
|
"upload": "上傳",
|
||||||
|
"migration": "還原備份:",
|
||||||
|
"createAccount": "建立帳戶",
|
||||||
|
"successful": "設定成功",
|
||||||
|
"hostDesc": "客戶端連接的公共主機名稱",
|
||||||
|
"portDesc": "客戶端和WireGuard使用的公共UDP連接埠"
|
||||||
|
},
|
||||||
|
"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": "IP地址",
|
||||||
|
"serverAllowedIps": "伺服器IP白名單",
|
||||||
|
"otlDesc": "生成短暫單次超連結",
|
||||||
|
"permanent": "永久",
|
||||||
|
"createdOn": "建立於",
|
||||||
|
"lastSeen": "上次活動於",
|
||||||
|
"totalDownload": "總下載量:",
|
||||||
|
"totalUpload": "總上傳量:",
|
||||||
|
"newClient": "新客戶端",
|
||||||
|
"disableClient": "停用客戶端",
|
||||||
|
"enableClient": "啟用客戶端",
|
||||||
|
"noPrivKey": "此客戶端沒有已知的密匙。無法建立配置。",
|
||||||
|
"showQR": "顯示二維碼",
|
||||||
|
"downloadConfig": "下載配置",
|
||||||
|
"allowedIpsDesc": "通過VPN的IP地址(取代全局配置)",
|
||||||
|
"serverAllowedIpsDesc": "伺服器路由到客戶端的IP地址",
|
||||||
|
"mtuDesc": "設定VPN隧道的最大傳輸單位(封包大小)",
|
||||||
|
"persistentKeepaliveDesc": "設定保持連線封包的秒數間隔。0代表停用",
|
||||||
|
"hooks": "掛鉤",
|
||||||
|
"hooksDescription": "掛鉤僅適用於wg-quick",
|
||||||
|
"hooksLeaveEmpty": "僅適用於wg-quick,否則請留空",
|
||||||
|
"dnsDesc": "客戶端使用的域名系統伺服器(取代全局配置)"
|
||||||
|
},
|
||||||
|
"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": "客戶端使用的域名系統伺服器(全局配置)",
|
||||||
|
"mtuDesc": "客戶端使用的MTU(僅適用於新客戶端)",
|
||||||
|
"persistentKeepaliveDesc": "向伺服器發送保持連線封包的秒數間隔。0代表停用(僅適用於新客戶端)",
|
||||||
|
"suggest": "建議",
|
||||||
|
"suggestDesc": "選擇一個IP地址或主機名稱"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"cidrSuccess": "成功更改CIDR",
|
||||||
|
"device": "裝置",
|
||||||
|
"deviceDesc": "WireGuard流量通過的乙太網路裝置",
|
||||||
|
"mtuDesc": "WireGuard使用的MTU",
|
||||||
|
"portDesc": "WireGuard監聽的UDP連接埠 (你可能也想更改設定連接埠)",
|
||||||
|
"changeCidr": "更改CIDR",
|
||||||
|
"restart": "重啟介面",
|
||||||
|
"restartDesc": "重啟WireGuard介面",
|
||||||
|
"restartWarn": "你確定要重新啟動介面嗎?這將中斷所有客戶端連線。",
|
||||||
|
"restartSuccess": "介面已重啟"
|
||||||
|
},
|
||||||
|
"introText": "歡迎來到管理員版面。\n\n你可以在側邊欄中管理一般、配置、介面和掛鉤設定。"
|
||||||
|
},
|
||||||
|
"zod": {
|
||||||
|
"generic": {
|
||||||
|
"required": "{0}為必填項",
|
||||||
|
"validNumber": "{0}必須是有效的數字",
|
||||||
|
"validString": "{0}必須是有效的字串",
|
||||||
|
"validBoolean": "{0}必須是有效的布林值",
|
||||||
|
"validArray": "{0}必須是有效的陣列",
|
||||||
|
"stringMin": "{0}至少需要{1}個字元",
|
||||||
|
"numberMin": "{0}至少為{1}"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"id": "客戶端ID",
|
||||||
|
"name": "名稱",
|
||||||
|
"expiresAt": "到期於",
|
||||||
|
"address4": "IPv4地址",
|
||||||
|
"address6": "IPv6地址",
|
||||||
|
"serverAllowedIps": "伺服器IP白名單"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"username": "用戶名",
|
||||||
|
"password": "密碼",
|
||||||
|
"remember": "保持",
|
||||||
|
"name": "名稱",
|
||||||
|
"email": "電郵",
|
||||||
|
"emailInvalid": "必須是有效的電郵地址",
|
||||||
|
"passwordMatch": "密碼必須一致",
|
||||||
|
"totpEnable": "TOTP啟用",
|
||||||
|
"totpEnableTrue": "TOTP啟用必須為正確",
|
||||||
|
"totpCode": "TOTP驗證碼"
|
||||||
|
},
|
||||||
|
"userConfig": {
|
||||||
|
"host": "主機"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"sessionTimeout": "工作階段逾時",
|
||||||
|
"metricsEnabled": "指標",
|
||||||
|
"metricsPassword": "指標密碼"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"cidr": "CIDR",
|
||||||
|
"device": "裝置",
|
||||||
|
"cidrValid": "CIDR必須有效"
|
||||||
|
},
|
||||||
|
"otl": "單次超連結",
|
||||||
|
"stringMalformed": "字串格式不正確",
|
||||||
|
"body": "主體必須是有效的物件",
|
||||||
|
"hook": "掛鉤",
|
||||||
|
"enabled": "已啟動",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"port": "連接埠",
|
||||||
|
"persistentKeepalive": "保持連線",
|
||||||
|
"address": "IP地址",
|
||||||
|
"dns": "域名系統",
|
||||||
|
"allowedIps": "IP白名單",
|
||||||
|
"file": "文件"
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"preUp": "PreUp",
|
||||||
|
"postUp": "PostUp",
|
||||||
|
"preDown": "PreDown",
|
||||||
|
"postDown": "PostDown"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,31 @@ export default defineNuxtConfig({
|
|||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
name: 'English',
|
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: 'zh-CN',
|
||||||
|
language: 'zh-CN',
|
||||||
|
name: '简体中文',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
vueI18n: './i18n.config.ts',
|
vueI18n: './i18n.config.ts',
|
||||||
@@ -41,6 +66,9 @@ export default defineNuxtConfig({
|
|||||||
detectBrowserLanguage: {
|
detectBrowserLanguage: {
|
||||||
useCookie: true,
|
useCookie: true,
|
||||||
},
|
},
|
||||||
|
bundle: {
|
||||||
|
optimizeTranslationDirective: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
nitro: {
|
nitro: {
|
||||||
esbuild: {
|
esbuild: {
|
||||||
@@ -52,6 +80,9 @@ export default defineNuxtConfig({
|
|||||||
alias: {
|
alias: {
|
||||||
'#db': fileURLToPath(new URL('./server/database/', import.meta.url)),
|
'#db': fileURLToPath(new URL('./server/database/', import.meta.url)),
|
||||||
},
|
},
|
||||||
|
externals: {
|
||||||
|
traceInclude: [fileURLToPath(new URL('./cli/index.ts', import.meta.url))],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
alias: {
|
alias: {
|
||||||
// for typecheck reasons (https://github.com/nuxt/cli/issues/323)
|
// for typecheck reasons (https://github.com/nuxt/cli/issues/323)
|
||||||
|
|||||||
+31
-27
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "wg-easy",
|
"name": "wg-easy",
|
||||||
"version": "15.0.0-beta.12",
|
"version": "15.1.0",
|
||||||
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
|
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "nuxt build && pnpm cli:build",
|
||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
@@ -15,53 +15,57 @@
|
|||||||
"format:check": "prettier . --check",
|
"format:check": "prettier . --check",
|
||||||
"typecheck": "nuxt typecheck",
|
"typecheck": "nuxt typecheck",
|
||||||
"check:all": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm build",
|
"check:all": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm build",
|
||||||
"db:generate": "drizzle-kit generate"
|
"db:generate": "drizzle-kit generate",
|
||||||
|
"cli:build": "node cli/build.js",
|
||||||
|
"cli:dev": "tsx cli/index.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eschricht/nuxt-color-mode": "^1.1.5",
|
"@eschricht/nuxt-color-mode": "^1.1.5",
|
||||||
"@heroicons/vue": "^2.2.0",
|
"@heroicons/vue": "^2.2.0",
|
||||||
"@libsql/client": "^0.15.3",
|
"@libsql/client": "^0.15.9",
|
||||||
"@nuxtjs/i18n": "^9.5.3",
|
"@nuxtjs/i18n": "^9.5.6",
|
||||||
"@nuxtjs/tailwindcss": "^6.13.2",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
"@phc/format": "^1.0.0",
|
"@phc/format": "^1.0.0",
|
||||||
"@pinia/nuxt": "^0.11.0",
|
"@pinia/nuxt": "^0.11.1",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"apexcharts": "^4.5.0",
|
"apexcharts": "^4.7.0",
|
||||||
"argon2": "^0.41.1",
|
"argon2": "^0.43.0",
|
||||||
"basic-auth": "^2.0.1",
|
|
||||||
"cidr-tools": "^11.0.3",
|
"cidr-tools": "^11.0.3",
|
||||||
|
"citty": "^0.1.6",
|
||||||
|
"consola": "^3.4.2",
|
||||||
"crc-32": "^1.2.2",
|
"crc-32": "^1.2.2",
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.1",
|
||||||
"drizzle-orm": "^0.41.0",
|
"drizzle-orm": "^0.44.2",
|
||||||
"ip-bigint": "^8.2.1",
|
"ip-bigint": "^8.2.1",
|
||||||
"is-cidr": "^5.1.1",
|
"is-cidr": "^5.1.1",
|
||||||
"is-ip": "^5.0.1",
|
"is-ip": "^5.0.1",
|
||||||
"js-sha256": "^0.11.0",
|
"js-sha256": "^0.11.1",
|
||||||
"lowdb": "^7.0.1",
|
"nuxt": "^3.17.5",
|
||||||
"nuxt": "^3.16.2",
|
|
||||||
"otpauth": "^9.4.0",
|
"otpauth": "^9.4.0",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.3",
|
||||||
"qr": "^0.4.0",
|
"qr": "^0.5.0",
|
||||||
"radix-vue": "^1.9.17",
|
"radix-vue": "^1.9.17",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.2",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"timeago.js": "^4.0.2",
|
"timeago.js": "^4.0.2",
|
||||||
"vue": "latest",
|
"vue": "latest",
|
||||||
"vue3-apexcharts": "^1.8.0",
|
"vue3-apexcharts": "^1.8.0",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.25.67"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/eslint": "1.3.0",
|
"@nuxt/eslint": "^1.4.1",
|
||||||
"@types/debug": "^4.1.12",
|
"@types/debug": "^4.1.12",
|
||||||
"@types/phc__format": "^1.0.1",
|
"@types/phc__format": "^1.0.1",
|
||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
"drizzle-kit": "^0.30.6",
|
"drizzle-kit": "^0.31.4",
|
||||||
"eslint": "^9.24.0",
|
"esbuild": "^0.25.5",
|
||||||
"eslint-config-prettier": "^10.1.2",
|
"eslint": "^9.30.0",
|
||||||
"prettier": "^3.5.3",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier": "^3.6.2",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.13",
|
||||||
|
"tsx": "^4.20.3",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"vue-tsc": "^2.2.8"
|
"vue-tsc": "^2.2.10"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.8.0"
|
"packageManager": "pnpm@10.12.4"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+3181
-2367
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
CREATE TABLE `clients_table` (
|
CREATE TABLE `clients_table` (
|
||||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
`user_id` integer NOT NULL,
|
`user_id` integer NOT NULL,
|
||||||
|
`interface_id` text NOT NULL,
|
||||||
`name` text NOT NULL,
|
`name` text NOT NULL,
|
||||||
`ipv4_address` text NOT NULL,
|
`ipv4_address` text NOT NULL,
|
||||||
`ipv6_address` text NOT NULL,
|
`ipv6_address` text NOT NULL,
|
||||||
@@ -21,7 +22,8 @@ CREATE TABLE `clients_table` (
|
|||||||
`enabled` integer NOT NULL,
|
`enabled` integer NOT NULL,
|
||||||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
||||||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
||||||
FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON UPDATE cascade ON DELETE restrict
|
FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON UPDATE cascade ON DELETE restrict,
|
||||||
|
FOREIGN KEY (`interface_id`) REFERENCES `interfaces_table`(`name`) ON UPDATE cascade ON DELETE cascade
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `clients_table_ipv4_address_unique` ON `clients_table` (`ipv4_address`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `clients_table_ipv4_address_unique` ON `clients_table` (`ipv4_address`);--> statement-breakpoint
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "b812341a-1ec2-49a6-8bc8-0332f5b32df4",
|
"id": "dae61656-1107-4729-ac83-f43d4cab3881",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"tables": {
|
"tables": {
|
||||||
"clients_table": {
|
"clients_table": {
|
||||||
@@ -21,6 +21,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"interface_id": {
|
||||||
|
"name": "interface_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -197,6 +204,19 @@
|
|||||||
],
|
],
|
||||||
"onDelete": "restrict",
|
"onDelete": "restrict",
|
||||||
"onUpdate": "cascade"
|
"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": {},
|
"compositePrimaryKeys": {},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"id": "c4c5bfb7-a66c-4e6b-a15c-232b16689dcf",
|
"id": "78de2e52-c4a8-4900-86c5-92f34739623a",
|
||||||
"prevId": "b812341a-1ec2-49a6-8bc8-0332f5b32df4",
|
"prevId": "dae61656-1107-4729-ac83-f43d4cab3881",
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"tables": {
|
"tables": {
|
||||||
@@ -21,6 +21,13 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"interface_id": {
|
||||||
|
"name": "interface_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -197,6 +204,19 @@
|
|||||||
],
|
],
|
||||||
"onUpdate": "cascade",
|
"onUpdate": "cascade",
|
||||||
"onDelete": "restrict"
|
"onDelete": "restrict"
|
||||||
|
},
|
||||||
|
"clients_table_interface_id_interfaces_table_name_fk": {
|
||||||
|
"name": "clients_table_interface_id_interfaces_table_name_fk",
|
||||||
|
"tableFrom": "clients_table",
|
||||||
|
"columnsFrom": [
|
||||||
|
"interface_id"
|
||||||
|
],
|
||||||
|
"tableTo": "interfaces_table",
|
||||||
|
"columnsTo": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"onUpdate": "cascade",
|
||||||
|
"onDelete": "cascade"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {},
|
"compositePrimaryKeys": {},
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1743515334198,
|
"when": 1748426990392,
|
||||||
"tag": "0000_short_skin",
|
"tag": "0000_short_skin",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1743515338707,
|
"when": 1748427001203,
|
||||||
"tag": "0001_classy_the_stranger",
|
"tag": "0001_classy_the_stranger",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { sql, relations } from 'drizzle-orm';
|
import { sql, relations } from 'drizzle-orm';
|
||||||
import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||||
|
|
||||||
import { oneTimeLink, user } from '../../schema';
|
import { oneTimeLink, user, wgInterface } from '../../schema';
|
||||||
|
|
||||||
/** null means use value from userConfig */
|
/** null means use value from userConfig */
|
||||||
|
|
||||||
@@ -13,6 +13,12 @@ export const client = sqliteTable('clients_table', {
|
|||||||
onDelete: 'restrict',
|
onDelete: 'restrict',
|
||||||
onUpdate: 'cascade',
|
onUpdate: 'cascade',
|
||||||
}),
|
}),
|
||||||
|
interfaceId: text('interface_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => wgInterface.name, {
|
||||||
|
onDelete: 'cascade',
|
||||||
|
onUpdate: 'cascade',
|
||||||
|
}),
|
||||||
name: text().notNull(),
|
name: text().notNull(),
|
||||||
ipv4Address: text('ipv4_address').notNull().unique(),
|
ipv4Address: text('ipv4_address').notNull().unique(),
|
||||||
ipv6Address: text('ipv6_address').notNull().unique(),
|
ipv6Address: text('ipv6_address').notNull().unique(),
|
||||||
@@ -51,4 +57,8 @@ export const clientsRelations = relations(client, ({ one }) => ({
|
|||||||
fields: [client.userId],
|
fields: [client.userId],
|
||||||
references: [user.id],
|
references: [user.id],
|
||||||
}),
|
}),
|
||||||
|
interface: one(wgInterface, {
|
||||||
|
fields: [client.interfaceId],
|
||||||
|
references: [wgInterface.name],
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ export class ClientService {
|
|||||||
name,
|
name,
|
||||||
// TODO: properly assign user id
|
// TODO: properly assign user id
|
||||||
userId: 1,
|
userId: 1,
|
||||||
|
interfaceId: 'wg0',
|
||||||
expiresAt,
|
expiresAt,
|
||||||
privateKey,
|
privateKey,
|
||||||
publicKey,
|
publicKey,
|
||||||
@@ -171,6 +172,7 @@ export class ClientService {
|
|||||||
.values({
|
.values({
|
||||||
name,
|
name,
|
||||||
userId: 1,
|
userId: 1,
|
||||||
|
interfaceId: 'wg0',
|
||||||
privateKey,
|
privateKey,
|
||||||
publicKey,
|
publicKey,
|
||||||
preSharedKey,
|
preSharedKey,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export type CreateClientType = Omit<
|
|||||||
|
|
||||||
export type UpdateClientType = Omit<
|
export type UpdateClientType = Omit<
|
||||||
CreateClientType,
|
CreateClientType,
|
||||||
'privateKey' | 'publicKey' | 'preSharedKey' | 'userId'
|
'privateKey' | 'publicKey' | 'preSharedKey' | 'userId' | 'interfaceId'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const name = z
|
const name = z
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { drizzle } from 'drizzle-orm/libsql';
|
|||||||
import { migrate as drizzleMigrate } from 'drizzle-orm/libsql/migrator';
|
import { migrate as drizzleMigrate } from 'drizzle-orm/libsql/migrator';
|
||||||
import { createClient } from '@libsql/client';
|
import { createClient } from '@libsql/client';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
import * as schema from './schema';
|
import * as schema from './schema';
|
||||||
import { ClientService } from './repositories/client/service';
|
import { ClientService } from './repositories/client/service';
|
||||||
@@ -25,6 +26,11 @@ export async function connect() {
|
|||||||
await initialSetup(dbService);
|
await initialSetup(dbService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (WG_ENV.DISABLE_IPV6) {
|
||||||
|
DB_DEBUG('Warning: Disabling IPv6...');
|
||||||
|
await disableIpv6(db);
|
||||||
|
}
|
||||||
|
|
||||||
return dbService;
|
return dbService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,3 +114,48 @@ async function initialSetup(db: DBServiceType) {
|
|||||||
await db.general.setSetupStep(0);
|
await db.general.setSetupStep(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function disableIpv6(db: DBType) {
|
||||||
|
// This should match the initial value migration
|
||||||
|
const postUpMatch =
|
||||||
|
' ip6tables -t nat -A POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE; 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;';
|
||||||
|
const postDownMatch =
|
||||||
|
' ip6tables -t nat -D POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE; 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;';
|
||||||
|
|
||||||
|
await db.transaction(async (tx) => {
|
||||||
|
const hooks = await tx.query.hooks.findFirst({
|
||||||
|
where: eq(schema.hooks.id, 'wg0'),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hooks) {
|
||||||
|
throw new Error('Hooks not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hooks.postUp.includes(postUpMatch)) {
|
||||||
|
DB_DEBUG('Disabling IPv6 in Post Up hooks...');
|
||||||
|
await tx
|
||||||
|
.update(schema.hooks)
|
||||||
|
.set({
|
||||||
|
postUp: hooks.postUp.replace(postUpMatch, ''),
|
||||||
|
postDown: hooks.postDown.replace(postDownMatch, ''),
|
||||||
|
})
|
||||||
|
.where(eq(schema.hooks.id, 'wg0'))
|
||||||
|
.execute();
|
||||||
|
} else {
|
||||||
|
DB_DEBUG('IPv6 Post Up hooks already disabled, skipping...');
|
||||||
|
}
|
||||||
|
if (hooks.postDown.includes(postDownMatch)) {
|
||||||
|
DB_DEBUG('Disabling IPv6 in Post Down hooks...');
|
||||||
|
await tx
|
||||||
|
.update(schema.hooks)
|
||||||
|
.set({
|
||||||
|
postUp: hooks.postUp.replace(postUpMatch, ''),
|
||||||
|
postDown: hooks.postDown.replace(postDownMatch, ''),
|
||||||
|
})
|
||||||
|
.where(eq(schema.hooks.id, 'wg0'))
|
||||||
|
.execute();
|
||||||
|
} else {
|
||||||
|
DB_DEBUG('IPv6 Post Down hooks already disabled, skipping...');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,13 +25,21 @@ class WireGuard {
|
|||||||
const hooks = await Database.hooks.get();
|
const hooks = await Database.hooks.get();
|
||||||
|
|
||||||
const result = [];
|
const result = [];
|
||||||
result.push(wg.generateServerInterface(wgInterface, hooks));
|
result.push(
|
||||||
|
wg.generateServerInterface(wgInterface, hooks, {
|
||||||
|
enableIpv6: !WG_ENV.DISABLE_IPV6,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
for (const client of clients) {
|
for (const client of clients) {
|
||||||
if (!client.enabled) {
|
if (!client.enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
result.push(wg.generateServerPeer(client));
|
result.push(
|
||||||
|
wg.generateServerPeer(client, {
|
||||||
|
enableIpv6: !WG_ENV.DISABLE_IPV6,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push('');
|
result.push('');
|
||||||
@@ -125,14 +133,16 @@ class WireGuard {
|
|||||||
throw new Error('Client not found');
|
throw new Error('Client not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return wg.generateClientConfig(wgInterface, userConfig, client);
|
return wg.generateClientConfig(wgInterface, userConfig, client, {
|
||||||
|
enableIpv6: !WG_ENV.DISABLE_IPV6,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getClientQRCodeSVG({ clientId }: { clientId: ID }) {
|
async getClientQRCodeSVG({ clientId }: { clientId: ID }) {
|
||||||
const config = await this.getClientConfiguration({ clientId });
|
const config = await this.getClientConfiguration({ clientId });
|
||||||
return encodeQR(config, 'svg', {
|
return encodeQR(config, 'svg', {
|
||||||
ecc: 'high',
|
ecc: 'high',
|
||||||
scale: 4,
|
scale: 2,
|
||||||
encoding: 'byte',
|
encoding: 'byte',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export const WG_ENV = {
|
|||||||
INSECURE: process.env.INSECURE === 'true',
|
INSECURE: process.env.INSECURE === 'true',
|
||||||
/** Port the UI is listening on */
|
/** Port the UI is listening on */
|
||||||
PORT: assertEnv('PORT'),
|
PORT: assertEnv('PORT'),
|
||||||
|
/** If IPv6 should be disabled */
|
||||||
|
DISABLE_IPV6: process.env.DISABLE_IPV6 === 'true',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WG_INITIAL_ENV = {
|
export const WG_INITIAL_ENV = {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// ! Auto Imports are not supported in this file
|
||||||
|
|
||||||
import argon2 from 'argon2';
|
import argon2 from 'argon2';
|
||||||
import { deserialize } from '@phc/format';
|
import { deserialize } from '@phc/format';
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,20 @@ import type { InterfaceType } from '#db/repositories/interface/types';
|
|||||||
import type { UserConfigType } from '#db/repositories/userConfig/types';
|
import type { UserConfigType } from '#db/repositories/userConfig/types';
|
||||||
import type { HooksType } from '#db/repositories/hooks/types';
|
import type { HooksType } from '#db/repositories/hooks/types';
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
enableIpv6?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const wg = {
|
export const wg = {
|
||||||
generateServerPeer: (client: Omit<ClientType, 'createdAt' | 'updatedAt'>) => {
|
generateServerPeer: (
|
||||||
|
client: Omit<ClientType, 'createdAt' | 'updatedAt'>,
|
||||||
|
options: Options = {}
|
||||||
|
) => {
|
||||||
|
const { enableIpv6 = true } = options;
|
||||||
|
|
||||||
const allowedIps = [
|
const allowedIps = [
|
||||||
`${client.ipv4Address}/32`,
|
`${client.ipv4Address}/32`,
|
||||||
`${client.ipv6Address}/128`,
|
...(enableIpv6 ? [`${client.ipv6Address}/128`] : []),
|
||||||
...(client.serverAllowedIps ?? []),
|
...(client.serverAllowedIps ?? []),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -25,19 +34,29 @@ PresharedKey = ${client.preSharedKey}
|
|||||||
AllowedIPs = ${allowedIps.join(', ')}${extraLines.length ? `\n${extraLines.join('\n')}` : ''}`;
|
AllowedIPs = ${allowedIps.join(', ')}${extraLines.length ? `\n${extraLines.join('\n')}` : ''}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
generateServerInterface: (wgInterface: InterfaceType, hooks: HooksType) => {
|
generateServerInterface: (
|
||||||
|
wgInterface: InterfaceType,
|
||||||
|
hooks: HooksType,
|
||||||
|
options: Options = {}
|
||||||
|
) => {
|
||||||
|
const { enableIpv6 = true } = options;
|
||||||
|
|
||||||
const cidr4 = parseCidr(wgInterface.ipv4Cidr);
|
const cidr4 = parseCidr(wgInterface.ipv4Cidr);
|
||||||
const cidr6 = parseCidr(wgInterface.ipv6Cidr);
|
const cidr6 = parseCidr(wgInterface.ipv6Cidr);
|
||||||
const ipv4Addr = stringifyIp({ number: cidr4.start + 1n, version: 4 });
|
const ipv4Addr = stringifyIp({ number: cidr4.start + 1n, version: 4 });
|
||||||
const ipv6Addr = stringifyIp({ number: cidr6.start + 1n, version: 6 });
|
const ipv6Addr = stringifyIp({ number: cidr6.start + 1n, version: 6 });
|
||||||
|
|
||||||
|
const address =
|
||||||
|
`${ipv4Addr}/${cidr4.prefix}` +
|
||||||
|
(enableIpv6 ? `, ${ipv6Addr}/${cidr6.prefix}` : '');
|
||||||
|
|
||||||
return `# Note: Do not edit this file directly.
|
return `# Note: Do not edit this file directly.
|
||||||
# Your changes will be overwritten!
|
# Your changes will be overwritten!
|
||||||
|
|
||||||
# Server
|
# Server
|
||||||
[Interface]
|
[Interface]
|
||||||
PrivateKey = ${wgInterface.privateKey}
|
PrivateKey = ${wgInterface.privateKey}
|
||||||
Address = ${ipv4Addr}/${cidr4.prefix}, ${ipv6Addr}/${cidr6.prefix}
|
Address = ${address}
|
||||||
ListenPort = ${wgInterface.port}
|
ListenPort = ${wgInterface.port}
|
||||||
MTU = ${wgInterface.mtu}
|
MTU = ${wgInterface.mtu}
|
||||||
PreUp = ${iptablesTemplate(hooks.preUp, wgInterface)}
|
PreUp = ${iptablesTemplate(hooks.preUp, wgInterface)}
|
||||||
@@ -49,11 +68,18 @@ PostDown = ${iptablesTemplate(hooks.postDown, wgInterface)}`;
|
|||||||
generateClientConfig: (
|
generateClientConfig: (
|
||||||
wgInterface: InterfaceType,
|
wgInterface: InterfaceType,
|
||||||
userConfig: UserConfigType,
|
userConfig: UserConfigType,
|
||||||
client: ClientType
|
client: ClientType,
|
||||||
|
options: Options = {}
|
||||||
) => {
|
) => {
|
||||||
|
const { enableIpv6 = true } = options;
|
||||||
|
|
||||||
const cidr4Block = parseCidr(wgInterface.ipv4Cidr).prefix;
|
const cidr4Block = parseCidr(wgInterface.ipv4Cidr).prefix;
|
||||||
const cidr6Block = parseCidr(wgInterface.ipv6Cidr).prefix;
|
const cidr6Block = parseCidr(wgInterface.ipv6Cidr).prefix;
|
||||||
|
|
||||||
|
const address =
|
||||||
|
`${client.ipv4Address}/${cidr4Block}` +
|
||||||
|
(enableIpv6 ? `, ${client.ipv6Address}/${cidr6Block}` : '');
|
||||||
|
|
||||||
const hookLines = [
|
const hookLines = [
|
||||||
client.preUp ? `PreUp = ${client.preUp}` : null,
|
client.preUp ? `PreUp = ${client.preUp}` : null,
|
||||||
client.postUp ? `PostUp = ${client.postUp}` : null,
|
client.postUp ? `PostUp = ${client.postUp}` : null,
|
||||||
@@ -63,7 +89,7 @@ PostDown = ${iptablesTemplate(hooks.postDown, wgInterface)}`;
|
|||||||
|
|
||||||
return `[Interface]
|
return `[Interface]
|
||||||
PrivateKey = ${client.privateKey}
|
PrivateKey = ${client.privateKey}
|
||||||
Address = ${client.ipv4Address}/${cidr4Block}, ${client.ipv6Address}/${cidr6Block}
|
Address = ${address}
|
||||||
DNS = ${(client.dns ?? userConfig.defaultDns).join(', ')}
|
DNS = ${(client.dns ?? userConfig.defaultDns).join(', ')}
|
||||||
MTU = ${client.mtu}
|
MTU = ${client.mtu}
|
||||||
${hookLines.length ? `${hookLines.join('\n')}\n` : ''}
|
${hookLines.length ? `${hookLines.join('\n')}\n` : ''}
|
||||||
|
|||||||
Reference in New Issue
Block a user