Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7964dc7993 | |||
| 0ac5d7d461 | |||
| 826914a4f3 | |||
| 261da431e7 | |||
| 94b33abf5e | |||
| 8325056ccc | |||
| 81a1b2c907 | |||
| fc8f89fb83 | |||
| d846c7745f | |||
| 61c6fd6c02 | |||
| abe5708058 | |||
| 626339bddb | |||
| 381ae23c07 | |||
| 52382d1d7a | |||
| 68e5216d4b | |||
| ceff95b336 | |||
| 782d1c215f | |||
| e8e26cfe10 | |||
| 400d4d992e | |||
| b08df55321 | |||
| b26a8110e0 | |||
| 692f550596 | |||
| badae8b8e4 | |||
| 7f89bde99e | |||
| 326717444b | |||
| 4e4bfc75e3 | |||
| 5c97a8ba73 | |||
| cba7a160ea | |||
| 4a75e1379d | |||
| 10a140d188 | |||
| edc3c5af57 | |||
| 26708305d6 | |||
| 6a282e6ab9 | |||
| a8ba7f7247 | |||
| 502fe718d5 | |||
| 5c7aac9fd2 | |||
| 2f96d9934b | |||
| daff15463d | |||
| 5f68d261c0 | |||
| 013ea6dba9 | |||
| ab9d75757f | |||
| 9be20109af | |||
| 9430b76258 | |||
| 99f1a004d5 | |||
| 2b42b639ea | |||
| 76d5944726 | |||
| 81bd19cfb6 | |||
| 0365ca7fb6 | |||
| 529d65b3fb | |||
| cbbf5d3d25 | |||
| 7b2d234ea5 | |||
| a282ca35f1 | |||
| 0792862c0d | |||
| 6c0d8e91fa | |||
| 8892c43a7d | |||
| 7cfe04286a | |||
| 000513f212 | |||
| 6ca3da1b80 | |||
| fe394ecbe4 | |||
| ec6f0423ca | |||
| e12208af75 | |||
| 2d9c75fd81 | |||
| 0c54b1c3da | |||
| be7943dc9b | |||
| 303c2f1e39 | |||
| 0b32ab899c | |||
| ef463d3d85 | |||
| c10daa2fd4 | |||
| cb8aa45cde | |||
| 54e0a1e886 | |||
| 71a452080e | |||
| 5be7fb3038 | |||
| 59f0c8b0d2 | |||
| e1ed93674d | |||
| 6b65a8099b | |||
| c1dd494d0f | |||
| bf9e8a6e21 | |||
| 371d7617ff | |||
| 0b435d9ed8 | |||
| 07f89d15a9 | |||
| b5318086d2 | |||
| b7f9b7c830 | |||
| 2e4f386f49 | |||
| 9ead985798 | |||
| 6326ee31c4 | |||
| 984dc95550 | |||
| cd0a9b8e33 | |||
| 90b9ba15ec | |||
| 0abc419db7 | |||
| b185d7a63d | |||
| 4bb880c4b7 | |||
| b0ba9e43f9 | |||
| ddb01fb968 | |||
| 22812e0632 | |||
| 4d84e1d9d3 | |||
| 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 | |||
| 3ef258a28a | |||
| ff783fd4d1 | |||
| 65aa067100 | |||
| 48e6949a4d | |||
| 9ebf2c1d33 | |||
| d0f85316a6 | |||
| ff9fd553c5 | |||
| e92ee0464e | |||
| 9df049d3f4 | |||
| 32b73b850a | |||
| 1c7f64ebd5 | |||
| 589ec1fe9a | |||
| 6e0d758e36 | |||
| 940edb2b0c | |||
| d51f12a82f | |||
| 4a3747fa12 | |||
| 499fb096b6 | |||
| c5c3a65bbf | |||
| c133446f9c | |||
| e8b3e54228 | |||
| a9a51337da | |||
| bbee7e04ed | |||
| 198b240755 | |||
| 86bdbe4c3d | |||
| 4890bb28e5 | |||
| c3dbd3a815 | |||
| fc480df910 | |||
| b3bd2502af | |||
| eb5ad91022 | |||
| f2955a1278 | |||
| 1b76c066e0 | |||
| 5b68cc7311 | |||
| 0597470f4c | |||
| 159a51cff4 | |||
| 9fc6ebafb3 | |||
| 9a029eeb23 | |||
| e5fb6ff3a6 | |||
| fcb5049dab |
@@ -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
|
||||
|
||||
@@ -27,17 +27,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -4,21 +4,38 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
name: Build & Deploy Docker
|
||||
runs-on: ubuntu-latest
|
||||
docker-build:
|
||||
name: Build Docker
|
||||
runs-on: ${{ matrix.arch.os }}
|
||||
if: github.repository_owner == 'wg-easy'
|
||||
permissions:
|
||||
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:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.arch.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- 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
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
@@ -27,15 +44,91 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
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
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ghcr.io/wg-easy/wg-easy:development
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=min
|
||||
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@v6
|
||||
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@v7
|
||||
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: Login to Codeberg
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: codeberg.org
|
||||
username: ${{ secrets.CODEBERG_USER }}
|
||||
password: ${{ secrets.CODEBERG_PASS }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
codeberg.org/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:
|
||||
name: Build & Deploy Docs
|
||||
@@ -43,12 +136,12 @@ jobs:
|
||||
if: github.repository_owner == 'wg-easy'
|
||||
permissions:
|
||||
contents: write
|
||||
needs: docker
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11.9
|
||||
cache: "pip"
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
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@v6
|
||||
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@v6
|
||||
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@v7
|
||||
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: Login to Codeberg
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: codeberg.org
|
||||
username: ${{ secrets.CODEBERG_USER }}
|
||||
password: ${{ secrets.CODEBERG_PASS }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
codeberg.org/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@v6
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
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,13 +11,25 @@ concurrency:
|
||||
jobs:
|
||||
docker:
|
||||
name: Build Docker
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.arch.os }}
|
||||
if: github.repository_owner == 'wg-easy'
|
||||
permissions:
|
||||
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:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.arch.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
@@ -37,7 +49,7 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: ${{ matrix.arch.platform }}
|
||||
tags: ghcr.io/wg-easy/wg-easy:pr
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=min
|
||||
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
|
||||
|
||||
+115
-29
@@ -6,21 +6,114 @@ on:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
# This workflow does not support fixing old versions
|
||||
# as this will break the latest and major tags
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
name: Build & Deploy Docker
|
||||
docker-build:
|
||||
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@v6
|
||||
|
||||
- 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@v6
|
||||
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' &&
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
needs: docker-build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-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: Login to Codeberg
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: codeberg.org
|
||||
username: ${{ secrets.CODEBERG_USER }}
|
||||
password: ${{ secrets.CODEBERG_PASS }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -31,28 +124,23 @@ jobs:
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
codeberg.org/wg-easy/wg-easy
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- 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: Build & Publish Docker Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
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
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
|
||||
|
||||
docs:
|
||||
name: Build & Deploy Docs
|
||||
@@ -62,12 +150,12 @@ jobs:
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
permissions:
|
||||
contents: write
|
||||
needs: docker
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11.9
|
||||
cache: "pip"
|
||||
@@ -87,11 +175,9 @@ jobs:
|
||||
cd docs
|
||||
git fetch origin gh-pages --depth=1 || true
|
||||
|
||||
# latest will point to old docs if old tag is pushed
|
||||
|
||||
# Extract version numbers
|
||||
DOCS_VERSION=${GITHUB_REF#refs/tags/} # e.g. v1.2.3 or v1.2.3-beta
|
||||
MINOR_VERSION=$(echo $DOCS_VERSION | cut -d. -f1,2) # e.g. v1.2
|
||||
DOCS_VERSION=${GITHUB_REF#refs/tags/} # e.g. v1.2.3 or v1.2.3-beta
|
||||
MINOR_VERSION=$(echo $DOCS_VERSION | cut -d. -f1,2) # e.g. v1.2
|
||||
|
||||
# Check if it's a stable release (only numbers, no '-')
|
||||
if [[ "$DOCS_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
|
||||
@@ -7,9 +7,36 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Check Docs
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'wg-easy'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "lts/jod"
|
||||
check-latest: true
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Check docs formatting
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm format:check:docs
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
needs: docs
|
||||
if: github.repository_owner == 'wg-easy'
|
||||
|
||||
strategy:
|
||||
@@ -20,7 +47,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -28,9 +55,9 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
node-version: "lts/jod"
|
||||
check-latest: true
|
||||
cache: "pnpm"
|
||||
|
||||
|
||||
@@ -15,13 +15,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'wg-easy'
|
||||
permissions:
|
||||
actions: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
# Stale after 30 days of inactivity
|
||||
days-before-issue-stale: 30
|
||||
# Close after 14 days of being stale
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||
@@ -32,3 +35,9 @@ jobs:
|
||||
close-pr-message: "This PR was closed because it has been inactive for 14 days since being marked as stale."
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
operations-per-run: 100
|
||||
# Ignore Feature requests (https://github.com/actions/stale/issues/1293)
|
||||
only-issue-types: "Bug"
|
||||
# Ignore confirmed bugs
|
||||
exempt-issue-labels: "status: confirmed"
|
||||
# Ignore PRs with milestones
|
||||
exempt-all-pr-milestones: true
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
.DS_Store
|
||||
*.swp
|
||||
node_modules
|
||||
Vendored
+2
-3
@@ -3,12 +3,11 @@
|
||||
"aaron-bond.better-comments",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"antfu.goto-alias",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"Nuxtr.nuxtr-vscode",
|
||||
"esbenp.prettier-vscode",
|
||||
"yoavbls.pretty-ts-errors",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"vue.volar",
|
||||
"lokalise.i18n-ally"
|
||||
"lokalise.i18n-ally",
|
||||
"DavidAnson.vscode-markdownlint"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+8
-3
@@ -3,9 +3,9 @@
|
||||
"editor.useTabStops": false,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"nuxtr.vueFiles.style.addStyleTag": false,
|
||||
"nuxtr.piniaFiles.defaultTemplate": "setup",
|
||||
"nuxtr.monorepoMode.DirectoryName": "src",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "always"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
@@ -15,6 +15,11 @@
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.tabSize": 4,
|
||||
"editor.useTabStops": false
|
||||
},
|
||||
"typescript.tsdk": "./src/node_modules/typescript/lib",
|
||||
"i18n-ally.enabledFrameworks": ["vue"],
|
||||
"i18n-ally.localesPaths": ["src/i18n/locales"],
|
||||
|
||||
+70
-2
@@ -7,10 +7,72 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [15.2.0] - 2026-01-12
|
||||
|
||||
### Added
|
||||
|
||||
- AmneziaWG integration (https://github.com/wg-easy/wg-easy/pull/2102, https://github.com/wg-easy/wg-easy/pull/2226)
|
||||
- Search / filter box (https://github.com/wg-easy/wg-easy/pull/2170)
|
||||
- `INIT_ALLOWED_IPS` env var (https://github.com/wg-easy/wg-easy/pull/2164)
|
||||
- Show client endpoint (https://github.com/wg-easy/wg-easy/pull/2058)
|
||||
- Add option to view and copy config (https://github.com/wg-easy/wg-easy/pull/2289)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix download as conf.txt (https://github.com/wg-easy/wg-easy/pull/2269)
|
||||
- Clean filename for OTL download (https://github.com/wg-easy/wg-easy/pull/2253)
|
||||
- Text color in admin menu in light mode (https://github.com/wg-easy/wg-easy/pull/2307)
|
||||
|
||||
### Changed
|
||||
|
||||
- Allow lower MTU (https://github.com/wg-easy/wg-easy/pull/2228)
|
||||
- Use /32 and /128 for client Cidr (https://github.com/wg-easy/wg-easy/pull/2217)
|
||||
- Return client id on create (https://github.com/wg-easy/wg-easy/pull/2190)
|
||||
- Publish on Codeberg (https://github.com/wg-easy/wg-easy/pull/2160)
|
||||
- Allow empty DNS (https://github.com/wg-easy/wg-easy/pull/2052, https://github.com/wg-easy/wg-easy/pull/2057)
|
||||
- Don't include keys in API responses (https://github.com/wg-easy/wg-easy/pull/2015)
|
||||
- Try all QR ecc levels (https://github.com/wg-easy/wg-easy/pull/2288)
|
||||
- Update OneTimeLink expiry on reuse (https://github.com/wg-easy/wg-easy/pull/2370)
|
||||
- Removed ARMv7 support (https://github.com/wg-easy/wg-easy/pull/2369)
|
||||
|
||||
### Docs
|
||||
|
||||
- Add AdGuard Home (https://github.com/wg-easy/wg-easy/pull/2175)
|
||||
- Add Routed (No NAT) docs (https://github.com/wg-easy/wg-easy/pull/2181, https://github.com/wg-easy/wg-easy/pull/2380)
|
||||
- Add AmneziaWG docs (https://github.com/wg-easy/wg-easy/pull/2108, https://github.com/wg-easy/wg-easy/pull/2292)
|
||||
|
||||
## [15.1.0] - 2025-07-01
|
||||
|
||||
### 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!
|
||||
This update is an entire rewrite to make it even easier to set up your own VPN.
|
||||
|
||||
## Major Changes
|
||||
### Breaking Changes
|
||||
|
||||
As the whole setup has changed, we recommend to start from scratch. And import your existing configs.
|
||||
|
||||
### Major Changes
|
||||
|
||||
- Almost all Environment variables removed
|
||||
- New and Improved UI
|
||||
@@ -23,7 +85,13 @@ This update is an entire rewrite to make it even easier to set up your own VPN.
|
||||
- SQLite Database
|
||||
- Deprecated Dockerless Installations
|
||||
- Added Docker Volume Mount (`/lib/modules`)
|
||||
- Removed ARMv6 and ARMv7 support
|
||||
- Removed ARMv6 support
|
||||
- Connections over HTTP require setting the `INSECURE` env var
|
||||
- Changed license from CC BY-NC-SA 4.0 to AGPL-3.0-only
|
||||
- Added 2FA using TOTP
|
||||
- Improved mobile support
|
||||
- CLI
|
||||
- Replaced `nightly` with `edge`
|
||||
|
||||
## [14.0.0] - 2024-09-04
|
||||
|
||||
|
||||
+25
-4
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/library/node:lts-alpine AS build
|
||||
FROM docker.io/library/node:jod-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
# update corepack
|
||||
@@ -14,9 +14,15 @@ RUN pnpm install
|
||||
COPY src ./
|
||||
RUN pnpm build
|
||||
|
||||
# Build amneziawg-tools
|
||||
RUN apk add linux-headers build-base git && \
|
||||
git clone https://github.com/amnezia-vpn/amneziawg-tools.git && \
|
||||
cd amneziawg-tools/src && \
|
||||
make
|
||||
|
||||
# Copy build result to a new image.
|
||||
# This saves a lot of disk space.
|
||||
FROM docker.io/library/node:lts-alpine
|
||||
FROM docker.io/library/node:jod-alpine
|
||||
WORKDIR /app
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=5s --retries=3 CMD /usr/bin/timeout 5s /bin/sh -c "/usr/bin/wg show | /bin/grep -q interface || exit 1"
|
||||
@@ -25,8 +31,17 @@ HEALTHCHECK --interval=1m --timeout=5s --retries=3 CMD /usr/bin/timeout 5s /bin/
|
||||
COPY --from=build /app/.output /app
|
||||
# Copy migrations
|
||||
COPY --from=build /app/server/database/migrations /app/server/database/migrations
|
||||
# libsql
|
||||
RUN npm install --no-save libsql
|
||||
# libsql (https://github.com/nitrojs/nitro/issues/3328)
|
||||
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
|
||||
# Copy amneziawg-tools
|
||||
COPY --from=build /app/amneziawg-tools/src/wg /usr/bin/awg
|
||||
COPY --from=build /app/amneziawg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
|
||||
RUN chmod +x /usr/bin/awg /usr/bin/awg-quick
|
||||
|
||||
# Install Linux packages
|
||||
RUN apk add --no-cache \
|
||||
@@ -34,10 +49,14 @@ RUN apk add --no-cache \
|
||||
dumb-init \
|
||||
iptables \
|
||||
ip6tables \
|
||||
nftables \
|
||||
kmod \
|
||||
iptables-legacy \
|
||||
wireguard-tools
|
||||
|
||||
RUN mkdir -p /etc/amnezia
|
||||
RUN ln -s /etc/wireguard /etc/amnezia/amneziawg
|
||||
|
||||
# Use iptables-legacy
|
||||
RUN update-alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables-legacy 10 --slave /usr/sbin/iptables-restore iptables-restore /usr/sbin/iptables-legacy-restore --slave /usr/sbin/iptables-save iptables-save /usr/sbin/iptables-legacy-save
|
||||
RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save
|
||||
@@ -47,6 +66,8 @@ ENV DEBUG=Server,WireGuard,Database,CMD
|
||||
ENV PORT=51821
|
||||
ENV HOST=0.0.0.0
|
||||
ENV INSECURE=false
|
||||
ENV INIT_ENABLED=false
|
||||
ENV DISABLE_IPV6=false
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/wg-easy/wg-easy
|
||||
|
||||
|
||||
+5
-2
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/library/node:lts-alpine
|
||||
FROM docker.io/library/node:jod-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# update corepack
|
||||
@@ -26,7 +26,9 @@ RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tab
|
||||
ENV DEBUG=Server,WireGuard,Database,CMD
|
||||
ENV PORT=51821
|
||||
ENV HOST=0.0.0.0
|
||||
ENV INSECURE=false
|
||||
ENV INSECURE=true
|
||||
ENV INIT_ENABLED=false
|
||||
ENV DISABLE_IPV6=false
|
||||
|
||||
# Install Dependencies
|
||||
COPY src/package.json src/pnpm-lock.yaml ./
|
||||
@@ -35,3 +37,4 @@ RUN pnpm install
|
||||
# Copy Project
|
||||
COPY src ./
|
||||
|
||||
ENTRYPOINT [ "pnpm", "run" ]
|
||||
|
||||
@@ -5,20 +5,14 @@
|
||||
[](https://github.com/wg-easy/wg-easy/stargazers)
|
||||
[](LICENSE)
|
||||
[](https://github.com/wg-easy/wg-easy/releases/latest)
|
||||
[](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)
|
||||
[](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy)
|
||||
|
||||
You have found the easiest way to install & manage WireGuard on any Linux host!
|
||||
|
||||
<!-- TOOD: update screenshot -->
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/screenshot.png" width="802" />
|
||||
<img src="./assets/screenshot.png" width="802" alt="wg-easy Screenshot" />
|
||||
</p>
|
||||
|
||||
## Features
|
||||
@@ -38,35 +32,27 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
|
||||
- Prometheus metrics support
|
||||
- IPv6 support
|
||||
- CIDR support
|
||||
- 2FA support
|
||||
|
||||
> [!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)
|
||||
|
||||
- [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/)
|
||||
- [Basic Installation](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/)
|
||||
- [Caddy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/caddy/)
|
||||
- [Traefik](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/traefik/)
|
||||
- [Podman](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/podman-nft/)
|
||||
- [AdGuard Home](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/adguard/)
|
||||
|
||||
## Requirements
|
||||
|
||||
- A host with a kernel that supports WireGuard (all modern kernels).
|
||||
- A host with Docker installed.
|
||||
|
||||
## Versions
|
||||
|
||||
> 💡 We follow semantic versioning (semver)
|
||||
|
||||
We offer multiple Docker image tags to suit your needs. The table below is in a particular order, with the first tag being the most recommended:
|
||||
|
||||
| tag | Branch | 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 |
|
||||
| `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. |
|
||||
| `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 |
|
||||
| `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). |
|
||||
| `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). |
|
||||
> [!NOTE]
|
||||
> If you want to migrate from the old version to the new version, you can find the migration guide here: [Migration Guide](https://wg-easy.github.io/wg-easy/latest/advanced/migrate/)
|
||||
|
||||
## Installation
|
||||
|
||||
This is a quick start guide to get you up and running with WireGuard Easy.
|
||||
|
||||
For a more detailed installation guide, please refer to the [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/) page.
|
||||
|
||||
### 1. Install Docker
|
||||
|
||||
If you haven't installed Docker yet, install it by running as root:
|
||||
@@ -82,59 +68,13 @@ And log in again.
|
||||
|
||||
The easiest way to run WireGuard Easy is with Docker Compose.
|
||||
|
||||
Just download [`docker-compose.yml`](docker-compose.yml), make necessary adjustments and
|
||||
execute `sudo docker compose up -d`.
|
||||
Just follow [these steps](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/) in the detailed documentation.
|
||||
|
||||
Now setup a reverse proxy to be able to access the Web UI from the internet.
|
||||
You can also install WireGuard Easy with the [docker run command](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/docker-run/) or via [podman](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/podman-nft/).
|
||||
|
||||
If you want to access the Web UI over HTTP, change the env var `INSECURE` to `true`. This is not recommended. Only use this for testing
|
||||
Now [setup a reverse proxy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/#setup-reverse-proxy) to be able to access the Web UI securely from the internet. This step is optional, just make sure to follow the guide [here](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/reverse-proxyless/) if you decide not to do it.
|
||||
|
||||
<!-- TOOD: add to docs: Grafana dashboard [21733](https://grafana.com/grafana/dashboards/21733-wireguard/) -->
|
||||
|
||||
<!-- TOOD: add to docs
|
||||
To setup the IPv6 Network, simply run once:
|
||||
|
||||
```bash
|
||||
docker network create \
|
||||
-d bridge --ipv6 \
|
||||
-d default \
|
||||
--subnet 10.42.42.0/24 \
|
||||
--subnet fdcc:ad94:bacf:61a3::/64 wg \
|
||||
```
|
||||
|
||||
To automatically install & run wg-easy, simply run:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--net wg \
|
||||
-e PORT=51821 \
|
||||
--name wg-easy \
|
||||
--ip6 fdcc:ad94:bacf:61a3::2a \
|
||||
--ip 10.42.42.42 \
|
||||
-v ~/.wg-easy:/etc/wireguard \
|
||||
-v /lib/modules:/lib/modules:ro \
|
||||
-p 51820:51820/udp \
|
||||
-p 51821:51821/tcp \
|
||||
--cap-add NET_ADMIN \
|
||||
--cap-add SYS_MODULE \
|
||||
--sysctl net.ipv4.ip_forward=1 \
|
||||
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
|
||||
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
|
||||
--sysctl net.ipv6.conf.all.forwarding=1 \
|
||||
--sysctl net.ipv6.conf.default.forwarding=1 \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
```
|
||||
|
||||
The Web UI will now be available on `http://0.0.0.0:51821`.
|
||||
|
||||
The Prometheus metrics will now be available on `http://0.0.0.0:51821/api/metrics`. Grafana dashboard [21733](https://grafana.com/grafana/dashboards/21733-wireguard/)
|
||||
|
||||
> 💡 Your configuration files will be saved in `~/.wg-easy`
|
||||
|
||||
-->
|
||||
|
||||
### 3. Sponsor
|
||||
## Donate
|
||||
|
||||
Are you enjoying this project? Consider donating.
|
||||
|
||||
@@ -142,46 +82,39 @@ Founder: [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻
|
||||
|
||||
Maintainer: [Buy kaaax0815 a coffee!](https://github.com/sponsors/kaaax0815) ☕
|
||||
|
||||
<!-- TOOD: add to docs
|
||||
## Development
|
||||
|
||||
## Options
|
||||
### Prerequisites
|
||||
|
||||
These options can be configured by setting environment variables using `-e KEY="VALUE"` in the `docker run` command.
|
||||
- Docker
|
||||
- Node LTS & corepack enabled
|
||||
- Visual Studio Code
|
||||
|
||||
| Env | Default | Example | Description |
|
||||
| ---------- | --------- | ----------- | ------------------------------ |
|
||||
| `PORT`. | `51821` | `6789` | TCP port for Web UI. |
|
||||
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
|
||||
| `INSECURE` | `false` | `true` | If access over http is allowed |
|
||||
### Dev Server
|
||||
|
||||
## Updating
|
||||
|
||||
To update to the latest version, simply run:
|
||||
This starts the development server with docker
|
||||
|
||||
```shell
|
||||
docker stop wg-easy
|
||||
docker rm wg-easy
|
||||
docker pull ghcr.io/wg-easy/wg-easy
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
And then run the `docker run -d \ ...` command above again.
|
||||
### Update Auto Imports
|
||||
|
||||
With Docker Compose WireGuard Easy can be updated with a single command:
|
||||
`docker compose up --detach --pull always` (if an image tag is specified in the
|
||||
Compose file and it is not `latest`, make sure that it is changed to the desired
|
||||
one; by default it is omitted and
|
||||
[defaults to `latest`](https://docs.docker.com/engine/reference/run/#image-references)). \
|
||||
The WireGuard Easy container will be automatically recreated if a newer image
|
||||
was pulled.
|
||||
If you add something that should be auto-importable and VSCode complains, run:
|
||||
|
||||
## Common Use Cases
|
||||
```shell
|
||||
cd src
|
||||
pnpm install
|
||||
cd ..
|
||||
```
|
||||
|
||||
- [Using WireGuard-Easy with Pi-Hole](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-Pi-Hole)
|
||||
- [Using WireGuard-Easy with nginx/SSL](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-nginx-SSL)
|
||||
### Test Cli
|
||||
|
||||
For less common or specific edge-case scenarios, please refer to the detailed information provided in the [Wiki](https://github.com/wg-easy/wg-easy/wiki).
|
||||
This starts the cli with docker
|
||||
|
||||
-->
|
||||
```shell
|
||||
pnpm cli:dev
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 185 KiB |
@@ -2,7 +2,7 @@ services:
|
||||
wg-easy:
|
||||
build:
|
||||
dockerfile: ./Dockerfile.dev
|
||||
command: pnpm run dev
|
||||
command: dev
|
||||
volumes:
|
||||
- ./src/:/app/
|
||||
- temp:/app/.nuxt/
|
||||
@@ -15,6 +15,12 @@ services:
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
environment:
|
||||
- INIT_ENABLED=true
|
||||
- INIT_HOST=test
|
||||
- INIT_PORT=51820
|
||||
- INIT_USERNAME=testtest
|
||||
- INIT_PASSWORD=Qweasdyxcv!2
|
||||
|
||||
# folders should be generated inside container
|
||||
volumes:
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ services:
|
||||
# - HOST=0.0.0.0
|
||||
# - INSECURE=false
|
||||
|
||||
image: ghcr.io/wg-easy/wg-easy
|
||||
image: ghcr.io/wg-easy/wg-easy:15
|
||||
container_name: wg-easy
|
||||
networks:
|
||||
wg:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -2,4 +2,42 @@
|
||||
title: API
|
||||
---
|
||||
|
||||
TODO
|
||||
/// 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.
|
||||
|
||||
There is no documentation for the API yet, but this will be added as the underlying library supports it.
|
||||
|
||||
## Authentication
|
||||
|
||||
To use the API, you need to authenticate using Basic Authentication. The username and password are the same as the ones you use to log in to the web application.
|
||||
If you use 2FA, the API will not work. You need to disable 2FA in the web application to use the API.
|
||||
|
||||
### Authentication Example
|
||||
|
||||
```python
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
url = "https://example.com:51821/api/client"
|
||||
response = requests.get(url, auth=HTTPBasicAuth('username', 'password'))
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(data)
|
||||
else:
|
||||
print(f"Error: {response.status_code}")
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
The Endpoints are not yet documented. But as file-based routing is used, you can find the endpoints in the `src/server/api` folder. The method is defined in the file name.
|
||||
|
||||
### Endpoints Example
|
||||
|
||||
| File Name | Endpoint | Method |
|
||||
| -------------------------------- | -------------- | ------ |
|
||||
| `src/server/api/client.get.ts` | `/api/client` | GET |
|
||||
| `src/server/api/setup/2.post.ts` | `/api/setup/2` | POST |
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: AmneziaWG
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
**AmneziaWG** is a modified version of the WireGuard protocol with enhanced traffic obfuscation capabilities. AmneziaWG's primary goal is to counter deep packet inspection (DPI) systems and bypass VPN blocking.
|
||||
|
||||
AmneziaWG adds multi-level transport-layer obfuscation by:
|
||||
|
||||
- Modifying packet headers
|
||||
- Randomizing handshake message sizes
|
||||
- Disguising traffic to resemble popular UDP protocols
|
||||
|
||||
These measures make it harder for third parties to analyze or identify your traffic, enhancing both privacy and security.
|
||||
|
||||
## Activating AmneziaWG
|
||||
|
||||
You must install the [AmneziaWG kernel module](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module) on the host system.
|
||||
|
||||
Experimental support for AmneziaWG can be enabled by setting the `EXPERIMENTAL_AWG` environment variable to `true`. Starting from wg-easy version 16, this setting will be enabled by default. This feature is still under development and may change in future releases.
|
||||
|
||||
When enabled, wg-easy will automatically detect whether the AmneziaWG kernel module is available. If it is not, the system will fall back to the standard WireGuard module.
|
||||
|
||||
To override this automatic detection, set the `OVERRIDE_AUTO_AWG` environment variable. By default, this variable is unset.
|
||||
|
||||
Possible values:
|
||||
|
||||
- `awg` — Force use of AmneziaWG
|
||||
- `wg` — Force use of standard WireGuard
|
||||
|
||||
## AmneziaWG Parameters
|
||||
|
||||
Parameter descriptions can be found in the [AmneziaWG documentation](https://docs.amnezia.org/documentation/amnezia-wg) and on the [kernel module page](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module).
|
||||
|
||||
All parameters except I1-I5 will be set at first startup. For information on how to set I1-I5 parameters, refer to the [AmneziaWG documentation](https://docs.amnezia.org/documentation/instructions/new-amneziawg-selfhosted/#how-to-extract-a-protocol-signature-for-amneziawg-15-manually).
|
||||
|
||||
If a parameter is not set, it will not be added to the configuration. If all AmneziaWG-specific parameters are absent, AmneziaWG will be fully compatible with standard WireGuard.
|
||||
|
||||
### Parameter Compatibility Table
|
||||
|
||||
| Parameter | Can differ between server and client | Configurable on server | Configurable on client |
|
||||
| --------- | ------------------------------------ | ---------------------- | ----------------------- |
|
||||
| Jc | ✅ Yes | ✅ | ✅ |
|
||||
| Jmin | ✅ Yes | ✅ | ✅ |
|
||||
| Jmax | ✅ Yes | ✅ | ✅ |
|
||||
| S1-S4 | ❌ No, must match | ✅ | ❌ (copied from server) |
|
||||
| H1-H4 | ❌ No, must match | ✅ | ❌ (copied from server) |
|
||||
| I1-I5 | ✅ Yes | ✅ | ✅ |
|
||||
|
||||
## Client Applications
|
||||
|
||||
To be able to connect to wg-easy if AmneziaWG is enabled, you must have an AmneziaWG-compatible client. Currently, only WG Tunnel and Amnezia VPN supports AmneziaWG 1.5/2.0! AmneziaWG clients require building from source code.
|
||||
|
||||
Android:
|
||||
|
||||
- [Amnezia VPN](https://play.google.com/store/apps/details?id=org.amnezia.vpn) - Amnezia VPN Official Client
|
||||
- [AmneziaWG](https://play.google.com/store/apps/details?id=org.amnezia.awg) - AmneziaWG Official Client
|
||||
- [WG Tunnel](https://play.google.com/store/apps/details?id=com.zaneschepke.wireguardautotunnel) - Third Party Client
|
||||
|
||||
iOS and macOS:
|
||||
|
||||
- [Amnezia VPN](https://apps.apple.com/us/app/amneziavpn/id1600529900) - Amnezia VPN Official Client
|
||||
- [AmneziaWG](https://apps.apple.com/us/app/amneziawg/id6478942365) - AmneziaWG Official Client
|
||||
|
||||
Windows:
|
||||
|
||||
- [Amnezia VPN](https://amnezia.org/downloads) - Amnezia VPN Official Client
|
||||
- [AmneziaWG](https://github.com/amnezia-vpn/amneziawg-windows-client/releases) - AmneziaWG Official Client
|
||||
|
||||
Linux:
|
||||
|
||||
- [Amnezia VPN](https://amnezia.org/downloads) - Amnezia VPN Official Client
|
||||
- [amneziawg-tools](https://github.com/amnezia-vpn/amneziawg-tools) - AmneziaWG Tools
|
||||
|
||||
OpenWRT:
|
||||
|
||||
- [AmneziaWG OpenWRT](https://github.com/Slava-Shchipunov/awg-openwrt) - AmneziaWG OpenWRT Packages
|
||||
- [AmneziaWG OpenWRT](https://github.com/lolo6oT/awg-openwrt) - AmneziaWG OpenWRT Packages
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Experimental Configuration
|
||||
---
|
||||
|
||||
There are several experimental features that can be enabled by setting the appropriate environment variables. These features are not guaranteed to be stable and may change in future releases.
|
||||
|
||||
| Env | Default | Example | Description | Notes | More Info |
|
||||
| ---------------- | ------- | ------- | -------------------------------------- | --------------------------------------- | ------------------------ |
|
||||
| EXPERIMENTAL_AWG | false | true | Enables experimental AmneziaWG support | Planned to be enabled by default in v16 | [See here](./amnezia.md) |
|
||||
@@ -2,4 +2,21 @@
|
||||
title: Optional Configuration
|
||||
---
|
||||
|
||||
TODO
|
||||
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 |
|
||||
| -------------- | --------- | ----------- | ---------------------------------- |
|
||||
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
|
||||
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
|
||||
| `INSECURE` | `false` | `true` | If access over http is allowed |
|
||||
| `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.
|
||||
|
||||
///
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Unattended Setup
|
||||
---
|
||||
|
||||
If you want to run the setup without any user interaction, e.g. with a tool like Ansible, you can use these environment variables to configure the setup.
|
||||
|
||||
These will only be used during the first start of the container. After that, the setup will be disabled.
|
||||
|
||||
| Env | Example | Description | Group |
|
||||
| ------------------ | ---------------------------- | --------------------------------------------------------- | ----- |
|
||||
| `INIT_ENABLED` | `true` | Enables the below env vars | 0 |
|
||||
| `INIT_USERNAME` | `admin` | Sets admin username | 1 |
|
||||
| `INIT_PASSWORD` | `Se!ureP%ssw` | Sets admin password | 1 |
|
||||
| `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 1 |
|
||||
| `INIT_PORT` | `51820` | Port clients will connect to and wireguard will listen on | 1 |
|
||||
| `INIT_DNS` | `1.1.1.1,8.8.8.8` | Sets global dns setting | 2 |
|
||||
| `INIT_IPV4_CIDR` | `10.8.0.0/24` | Sets IPv4 cidr | 3 |
|
||||
| `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 3 |
|
||||
| `INIT_ALLOWED_IPS` | `10.8.0.0/24,2001:0DB8::/32` | Sets global Allowed IPs | 4 |
|
||||
|
||||
/// warning | Variables have to be used together
|
||||
|
||||
If variables are in the same group, you have to set all of them. For example, if you set `INIT_IPV4_CIDR`, you also have to set `INIT_IPV6_CIDR`.
|
||||
|
||||
If you want to skip the setup process, you have to configure group `1`
|
||||
///
|
||||
|
||||
/// note | Security
|
||||
|
||||
The initial username and password is not checked for complexity. Make sure to set a long enough username and password. Otherwise, the user won't be able to log in.
|
||||
|
||||
It's recommended to remove the variables after the setup is done to prevent the password from being exposed.
|
||||
///
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Prometheus
|
||||
---
|
||||
|
||||
To monitor the WireGuard server, you can use [Prometheus](https://prometheus.io/) and [Grafana](https://grafana.com/). The container exposes a `/metrics/prometheus` endpoint that can be scraped by Prometheus.
|
||||
|
||||
## Enable Prometheus
|
||||
|
||||
To enable Prometheus metrics, go to Admin Panel > General and enable Prometheus.
|
||||
|
||||
You can optionally set a Bearer Password for the metrics endpoints. This is useful if you want to expose the metrics endpoint to the internet.
|
||||
|
||||
## Configure Prometheus
|
||||
|
||||
You need to add a scrape config to your Prometheus configuration file. Here is an example:
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'wg-easy'
|
||||
scrape_interval: 30s
|
||||
metrics_path: /metrics/prometheus
|
||||
static_configs:
|
||||
- targets:
|
||||
- 'localhost:51821'
|
||||
authorization:
|
||||
type: Bearer
|
||||
credentials: 'SuperSecurePassword'
|
||||
```
|
||||
|
||||
## Grafana Dashboard
|
||||
|
||||
You can use the following Grafana dashboard to visualize the metrics:
|
||||
|
||||
[](https://grafana.com/grafana/dashboards/21733-wireguard/)
|
||||
|
||||
[21733](https://grafana.com/grafana/dashboards/21733-wireguard/)
|
||||
|
||||
/// note | Unofficial
|
||||
|
||||
The Grafana dashboard is not official and is not maintained by the `wg-easy` team. If you have any issues with the dashboard, please contact the author of the dashboard.
|
||||
See [#1299](https://github.com/wg-easy/wg-easy/pull/1299) for more information.
|
||||
///
|
||||
@@ -6,22 +6,24 @@ This guide will help you migrate from `v14` to version `v15` of `wg-easy`.
|
||||
|
||||
## Changes
|
||||
|
||||
- This is a complete rewrite of the `wg-easy` project. Therefore the configuration files and the way you interact with the project have changed.
|
||||
- If you use armv6 or armv7, you can't migrate to `v15` yet. We are working on it.
|
||||
- If you are connecting to the web ui via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container.
|
||||
- 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 unfortunately won't be able to migrate to `v15`.
|
||||
- If you are connecting to the Web UI via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container.
|
||||
|
||||
## Migration
|
||||
|
||||
### 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.
|
||||
|
||||
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
|
||||
|
||||
1. Stop the running container
|
||||
@@ -32,21 +34,25 @@ If you are using `docker run`
|
||||
docker stop wg-easy
|
||||
```
|
||||
|
||||
If you are using `docker-compose`
|
||||
If you are using `docker compose`
|
||||
|
||||
```shell
|
||||
docker-compose down
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### Start 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-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
|
||||
|
||||
You have now successfully migrated to `v15` of `wg-easy`.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Migrate
|
||||
---
|
||||
|
||||
If you want to migrate from an older version of `wg-easy` to the new version, you can find the migration guides listed below.
|
||||
|
||||
- [Migrate from v14 to v15](./from-14-to-15.md) : This guide should also work for any version before `v14`.
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"1": "Initial version. Enjoy!",
|
||||
"2": "You can now rename a client & update the address. Enjoy!",
|
||||
"3": "Many improvements and small changes. Enjoy!",
|
||||
"4": "Now with pretty charts for client's network speed. Enjoy!",
|
||||
"5": "Many small improvements & feature requests. Enjoy!",
|
||||
"6": "Many small performance improvements & bug fixes. Enjoy!",
|
||||
"7": "Improved the look & performance of the upload/download chart.",
|
||||
"8": "Updated to Node.js v18.",
|
||||
"9": "Fixed issue running on devices with older kernels.",
|
||||
"10": "Added sessionless HTTP API auth & automatic dark mode.",
|
||||
"11": "Multilanguage Support & various bugfixes.",
|
||||
"12": "UI_TRAFFIC_STATS, Import json configurations with no PreShared-Key, allow clients with no privateKey & more.",
|
||||
"13": "New framework (h3), UI_CHART_TYPE, some bugfixes & more.",
|
||||
"14": "Home Assistent support, PASSWORD_HASH (inc. Helper), translation updates bugfixes & more."
|
||||
}
|
||||
@@ -12,7 +12,7 @@ When refactoring, writing or altering files, adhere to these rules:
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ Maintainers take the time to improve on this project and help by solving issues
|
||||
|
||||
### Filing a Bug Report
|
||||
|
||||
Thank you for participating in this project and reporting a bug. wg-easy is a community-driven project, and each contribution counts!
|
||||
Thank you for participating in this project and reporting a bug. `wg-easy` is a community-driven project, and each contribution counts!
|
||||
|
||||
Maintainers and moderators are volunteers. We greatly appreciate reports that take the time to provide detailed information via the template, enabling us to help you in the best and quickest way. Ignoring the template provided may seem easier, but discourages receiving any support (_via assignment of the label `meta/no template - no support`_).
|
||||
Maintainers and moderators are volunteers. We greatly appreciate reports that take the time to provide detailed information via the template, enabling us to help you in the best and quickest way. Ignoring the template provided may seem easier, but discourages receiving any support.
|
||||
|
||||
Markdown formatting can be used in almost all text fields (_unless stated otherwise in the description_).
|
||||
|
||||
@@ -50,7 +50,7 @@ The development workflow is the following:
|
||||
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.
|
||||
|
||||
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
|
||||
[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.
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
title: AdGuard Home
|
||||
---
|
||||
|
||||
This tutorial is a follow-up to the official [Traefik tutorial](./traefik.md). It will guide you through integrating AdGuard Home with your existing `wg-easy` and Traefik setup to provide network-wide DNS ad-blocking.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A working [wg-easy](./basic-installation.md) and [Traefik](./traefik.md) setup from the previous guides.
|
||||
|
||||
/// warning | Important: Following this guide will reset your WireGuard configuration.
|
||||
The process involves re-creating the `wg-easy` container and its data, which means **all existing WireGuard clients and settings will be deleted.**
|
||||
|
||||
You will need to create your clients again after completing this guide.
|
||||
///
|
||||
|
||||
## Add `adguard` configuration
|
||||
|
||||
1. Create a directory for the configuration files:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /etc/docker/containers/adguard
|
||||
```
|
||||
|
||||
2. Create volumes for persistent data:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /etc/docker/volumes/adguard/adguard_work
|
||||
sudo mkdir -p /etc/docker/volumes/adguard/adguard_conf
|
||||
sudo chmod -R 700 /etc/docker/volumes/adguard
|
||||
```
|
||||
|
||||
3. Create the `docker-compose.yml` file.
|
||||
|
||||
File: `/etc/docker/containers/adguard/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
adguard:
|
||||
image: adguard/adguardhome:v0.107.64
|
||||
container_name: adguard
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /etc/docker/volumes/adguard/adguard_work:/opt/adguardhome/work
|
||||
- /etc/docker/volumes/adguard/adguard_conf:/opt/adguardhome/conf
|
||||
networks:
|
||||
wg:
|
||||
interface_name: eth0
|
||||
ipv4_address: 10.42.42.43
|
||||
ipv6_address: fdcc:ad94:bacf:61a3::2b
|
||||
traefik:
|
||||
interface_name: eth1
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.adguard.rule=Host(`adguard.$example.com$`)'
|
||||
- 'traefik.http.routers.adguard.entrypoints=websecure'
|
||||
- 'traefik.http.routers.adguard.service=adguard'
|
||||
- 'traefik.http.services.adguard.loadbalancer.server.port=3000'
|
||||
- 'traefik.docker.network=traefik'
|
||||
|
||||
networks:
|
||||
wg:
|
||||
external: true
|
||||
traefik:
|
||||
external: true
|
||||
```
|
||||
|
||||
## Update `wg-easy` configuration
|
||||
|
||||
Modify the corresponding sections of your existing `wg-easy` compose file to match the updated version below.
|
||||
|
||||
File: `/etc/docker/containers/wg-easy/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wg-easy:
|
||||
ports:
|
||||
- "51820:51820/udp"
|
||||
...
|
||||
networks:
|
||||
wg:
|
||||
interface_name: eth0
|
||||
...
|
||||
traefik:
|
||||
interface_name: eth1
|
||||
...
|
||||
...
|
||||
environment:
|
||||
# Unattended Setup
|
||||
- INIT_ENABLED=true
|
||||
# Replace $username$ with your username
|
||||
- INIT_USERNAME=$username$
|
||||
# Replace $password$ with your unhashed password
|
||||
- INIT_PASSWORD=$password$
|
||||
# Replace $example.com$ with your domain
|
||||
- INIT_HOST=wg-easy.$example.com$
|
||||
- INIT_PORT=51820
|
||||
- INIT_DNS=10.42.42.43,fdcc:ad94:bacf:61a3::2b
|
||||
- INIT_IPV4_CIDR=10.8.0.0/24
|
||||
- INIT_IPV6_CIDR=fd42:42:42::/64
|
||||
...
|
||||
|
||||
networks:
|
||||
wg:
|
||||
# Prevents Docker Compose from prefixing the network name.
|
||||
name: wg
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
## Setup Wireguard
|
||||
|
||||
1. Restart `wg-easy`:
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker compose down -v
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
2. Edit Wireguard's Hooks.
|
||||
|
||||
In the Admin Panel of your WireGuard server, go to the Hooks tab and replace it with:
|
||||
|
||||
**_PostUp_**
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -t nat -A PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination 10.42.42.43; iptables -t nat -A PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination 10.42.42.43; ip6tables -t nat -A PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b; ip6tables -t nat -A PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; ip6tables -t nat -A POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE;
|
||||
```
|
||||
|
||||
**_PostDown_**
|
||||
|
||||
```shell
|
||||
iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT || true; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT || true; iptables -t nat -D PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination 10.42.42.43 || true; iptables -t nat -D PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination 10.42.42.43 || true; ip6tables -t nat -D PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b || true; ip6tables -t nat -D PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b || true; iptables -D FORWARD -i wg0 -j ACCEPT || true; iptables -D FORWARD -o wg0 -j ACCEPT || true; ip6tables -D FORWARD -i wg0 -j ACCEPT || true; ip6tables -D FORWARD -o wg0 -j ACCEPT || true; iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE || true; ip6tables -t nat -D POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE || true;
|
||||
```
|
||||
|
||||
3. Restart `wg-easy` to apply changes:
|
||||
|
||||
```shell
|
||||
sudo docker restart wg-easy
|
||||
```
|
||||
|
||||
## Setup Adguard Home
|
||||
|
||||
1. Start `adguard` service:
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/adguard
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
2. Navigate to `https://adguard.$example.com$` to begin the AdGuard Home setup.
|
||||
|
||||
/// warning | Important: Configure AdGuard Home Admin Web Interface Port
|
||||
During the initial AdGuard Home setup on the `Step 2/5` page, you **must** set the **Admin Web Interface Port** to **3000**. Do not use the default port 80, as it will not work with the Traefik configuration.
|
||||
|
||||
After completing the setup, the AdGuard UI might appear unresponsive. This is expected. **Simply reload the page**, and the panel will display correctly.
|
||||
///
|
||||
|
||||
> If you accidentally left it default (80), you will need to manually edit the `docker-compose.yml` file for AdGuard Home (`/etc/docker/containers/adguard/docker-compose.yml`) and change the line `traefik.http.services.adguard.loadbalancer.server.port=3000` to `traefik.http.services.adguard.loadbalancer.server.port=80`. After making this change, restart AdGuard Home by navigating to `/etc/docker/containers/adguard` and running `sudo docker compose up -d`.
|
||||
|
||||
## Final System Checks
|
||||
|
||||
### Firewall
|
||||
|
||||
Ensure the ports `80/tcp`, `443/tcp`, `443/udp`, and `51820/udp` are open.
|
||||
|
||||
### Optional: Optimizing UDP Buffer Sizes
|
||||
|
||||
AdGuard Home, as a DNS server, handles a large volume of UDP packets. To ensure optimal performance, it is recommended to increase the system's UDP buffer sizes. You can apply these settings using your system's `sysctl` configuration (e.g., by creating a file in `/etc/sysctl.d/`).
|
||||
|
||||
```shell
|
||||
net.core.rmem_max = 7500000
|
||||
net.core.wmem_max = 7500000
|
||||
```
|
||||
|
||||
After adding these settings, remember to apply them (e.g., by running `sudo sysctl --system` or rebooting)
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Auto Updates
|
||||
---
|
||||
|
||||
## Docker Compose
|
||||
|
||||
With Docker Compose `wg-easy` can be updated with a single command:
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker compose up -d --pull always
|
||||
```
|
||||
|
||||
### Watchtower
|
||||
|
||||
If you want the updates to be fully automatic you can install Watchtower. This will check for updates every day at 4:00 AM and update the container if a new version is available.
|
||||
|
||||
File: `/etc/docker/containers/watchtower/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
watchtower:
|
||||
image: containrrr/watchtower:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
env_file:
|
||||
- watchtower.env
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
File: `/etc/docker/containers/watchtower/watchtower.env`
|
||||
|
||||
```env
|
||||
WATCHTOWER_CLEANUP=true
|
||||
WATCHTOWER_SCHEDULE=0 0 4 * * *
|
||||
TZ=Europe/Berlin
|
||||
|
||||
# Email
|
||||
# WATCHTOWER_NOTIFICATIONS_LEVEL=info
|
||||
# WATCHTOWER_NOTIFICATIONS=email
|
||||
# WATCHTOWER_NOTIFICATION_EMAIL_FROM=mail@example.com
|
||||
# WATCHTOWER_NOTIFICATION_EMAIL_TO=mail@example.com
|
||||
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.example.com
|
||||
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=mail@example.com
|
||||
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD="SuperSecurePassword"
|
||||
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587
|
||||
```
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/watchtower
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
## Docker Run
|
||||
|
||||
```shell
|
||||
sudo docker stop wg-easy
|
||||
sudo docker rm wg-easy
|
||||
sudo docker pull ghcr.io/wg-easy/wg-easy
|
||||
```
|
||||
|
||||
And then run the `docker run -d \ ...` command from [Docker Run][docker-run] again.
|
||||
|
||||
[docker-run]: ./docker-run.md
|
||||
|
||||
## Podman
|
||||
|
||||
To update `wg-easy` (and every container that has auto updates enabled), you can run the following command:
|
||||
|
||||
```shell
|
||||
sudo podman auto-update
|
||||
```
|
||||
@@ -19,38 +19,49 @@ Follow the Docs here: <https://docs.docker.com/engine/install/> and install Dock
|
||||
|
||||
1. Create a directory for the configuration files (you can choose any directory you like):
|
||||
|
||||
```shell
|
||||
DIR=/docker/wg-easy
|
||||
sudo mkdir -p $DIR
|
||||
```
|
||||
```shell
|
||||
sudo mkdir -p /etc/docker/containers/wg-easy
|
||||
```
|
||||
|
||||
2. Download docker compose file
|
||||
|
||||
```shell
|
||||
sudo curl -o $URL/docker-compose.yml https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml
|
||||
```
|
||||
```shell
|
||||
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml
|
||||
```
|
||||
|
||||
3. Start `wg-easy`
|
||||
|
||||
```shell
|
||||
sudo docker-compose -f $DIR/docker-compose.yml up -d
|
||||
```
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
## Setup Firewall
|
||||
|
||||
If you are using a firewall, you need to open the following ports:
|
||||
|
||||
- UDP 51820 (WireGuard)
|
||||
- TCP 51821 (Web UI)
|
||||
|
||||
These ports can be changed, so if you change them you have to update your firewall rules accordingly.
|
||||
|
||||
## Setup Reverse Proxy
|
||||
|
||||
TODO
|
||||
- To setup traefik follow the instructions here: [Traefik](./traefik.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)
|
||||
|
||||
## Access the Web UI
|
||||
## Update `wg-easy`
|
||||
|
||||
Open your browser and navigate to `https://<your-domain>:51821` or `https://<your-ip>:51821`.
|
||||
To update `wg-easy` to the latest version, run:
|
||||
|
||||
Follow the instructions to set up your WireGuard VPN.
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker compose pull
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
## Auto Update
|
||||
|
||||
If you want to enable auto-updates, follow the instructions here: [Auto Updates][auto-updates]
|
||||
|
||||
[auto-updates]: ./auto-updates.md
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
title: Caddy
|
||||
---
|
||||
|
||||
/// note | Opinionated
|
||||
|
||||
This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths.
|
||||
///
|
||||
|
||||
We're using [Caddy](https://caddyserver.com/) here as reverse proxy to serve `wg-easy` on [https://wg-easy.example.com](https://wg-easy.example.com) via TLS.
|
||||
|
||||
## Create a docker composition for `caddy`
|
||||
|
||||
```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,41 @@
|
||||
---
|
||||
title: Docker Run
|
||||
---
|
||||
|
||||
To setup the IPv6 Network, simply run once:
|
||||
|
||||
```shell
|
||||
docker network create \
|
||||
-d bridge --ipv6 \
|
||||
--subnet 10.42.42.0/24 \
|
||||
--subnet fdcc:ad94:bacf:61a3::/64 \
|
||||
wg
|
||||
```
|
||||
|
||||
<!-- ref: major version -->
|
||||
|
||||
To automatically install & run `wg-easy`, simply run:
|
||||
|
||||
```shell
|
||||
docker run -d \
|
||||
--net wg \
|
||||
-e INSECURE=true \
|
||||
--name wg-easy \
|
||||
--ip6 fdcc:ad94:bacf:61a3::2a \
|
||||
--ip 10.42.42.42 \
|
||||
-v ~/.wg-easy:/etc/wireguard \
|
||||
-v /lib/modules:/lib/modules:ro \
|
||||
-p 51820:51820/udp \
|
||||
-p 51821:51821/tcp \
|
||||
--cap-add NET_ADMIN \
|
||||
--cap-add SYS_MODULE \
|
||||
--sysctl net.ipv4.ip_forward=1 \
|
||||
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
|
||||
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
|
||||
--sysctl net.ipv6.conf.all.forwarding=1 \
|
||||
--sysctl net.ipv6.conf.default.forwarding=1 \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/wg-easy/wg-easy:15
|
||||
```
|
||||
|
||||
The Web UI will now be available at <http://0.0.0.0:51821>.
|
||||
@@ -2,4 +2,6 @@
|
||||
title: Without Docker
|
||||
---
|
||||
|
||||
TODO
|
||||
This is currently not yet supported.
|
||||
|
||||
<!-- TODO -->
|
||||
|
||||
+24
-12
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Podman
|
||||
title: Podman + nftables
|
||||
---
|
||||
|
||||
This guide will show you how to run `wg-easy` with rootful Podman and nftables.
|
||||
@@ -19,16 +19,23 @@ sudo mkdir -p /etc/containers/volumes/wg-easy
|
||||
|
||||
Create a file `/etc/containers/systemd/wg-easy/wg-easy.container` with the following content:
|
||||
|
||||
<!-- ref: major version -->
|
||||
|
||||
```ini
|
||||
[Container]
|
||||
ContainerName=wg-easy
|
||||
Image=ghcr.io/wg-easy/wg-easy:latest
|
||||
Image=ghcr.io/wg-easy/wg-easy:15
|
||||
AutoUpdate=registry
|
||||
|
||||
Volume=/etc/containers/volumes/wg-easy:/etc/wireguard:Z
|
||||
Network=wg-easy.network
|
||||
PublishPort=51820:51820/udp
|
||||
PublishPort=51821:51821/tcp
|
||||
|
||||
# this is used to allow access over HTTP
|
||||
# remove this when using a reverse proxy
|
||||
Environment=INSECURE=true
|
||||
|
||||
AddCapability=NET_ADMIN
|
||||
AddCapability=SYS_MODULE
|
||||
AddCapability=NET_RAW
|
||||
@@ -80,17 +87,22 @@ In the Admin Panel of your WireGuard server, go to the `Hooks` tab and add the f
|
||||
|
||||
1. PostUp
|
||||
|
||||
```shell
|
||||
apk add nftables; nft add table inet wg_table; nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \; }; nft add rule inet wg_table postrouting ip saddr {{ipv4Cidr}} oifname {{device}} masquerade; nft add rule inet wg_table postrouting ip6 saddr {{ipv6Cidr}} oifname {{device}} masquerade; nft add chain inet wg_table input { type filter hook input priority 0 \; policy drop \; }; nft add rule inet wg_table input udp dport {{port}} accept; nft add chain inet wg_table forward { type filter hook forward priority 0 \; policy drop \; }; nft add rule inet wg_table forward iifname "wg0" accept; nft add rule inet wg_table forward oifname "wg0" accept;
|
||||
```
|
||||
```shell
|
||||
nft add table inet wg_table; nft add chain inet wg_table prerouting { type nat hook prerouting priority 100 \; }; nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \; }; nft add rule inet wg_table postrouting ip saddr {{ipv4Cidr}} oifname {{device}} masquerade; nft add rule inet wg_table postrouting ip6 saddr {{ipv6Cidr}} oifname {{device}} masquerade; nft add chain inet wg_table input { type filter hook input priority 0 \; policy accept \; }; nft add rule inet wg_table input udp dport {{port}} accept; nft add rule inet wg_table input tcp dport {{uiPort}} accept; nft add chain inet wg_table forward { type filter hook forward priority 0 \; policy accept \; }; nft add rule inet wg_table forward iifname "wg0" accept; nft add rule inet wg_table forward oifname "wg0" accept;
|
||||
```
|
||||
|
||||
2. PostDown
|
||||
|
||||
```shell
|
||||
nft delete table inet wg_table
|
||||
```
|
||||
```shell
|
||||
nft delete table inet wg_table
|
||||
```
|
||||
|
||||
<!--
|
||||
TODO: improve docs after better nftables support
|
||||
TODO: fix accept web ui port
|
||||
-->
|
||||
If you don't have iptables loaded on your server, you could see many errors in the logs or in the UI. You can ignore them.
|
||||
|
||||
## Restart the Container
|
||||
|
||||
Restart the container to apply the new hooks:
|
||||
|
||||
```shell
|
||||
sudo systemctl restart wg-easy
|
||||
```
|
||||
@@ -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.
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: Routed setup (No NAT)
|
||||
---
|
||||
|
||||
This guide shows how to run **wg-easy** with a routed setup, so packets are forwarded instead of NATed.
|
||||
|
||||
In a routed design, each WireGuard client keeps its own IPv4/IPv6 address. That means you can identify clients by their real addresses instead of seeing everything as the WireGuard server’s IP.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. You know how to add static routes on your router to the WireGuard server.
|
||||
|
||||
## Docker setup
|
||||
|
||||
To make use of our own IPv4/IPv6 addresses, run the container with the `network_mode: host` option.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wg-easy:
|
||||
image: ghcr.io/wg-easy/wg-easy:15
|
||||
container_name: wg-easy
|
||||
network_mode: 'host'
|
||||
volumes:
|
||||
- ./config:/etc/wireguard
|
||||
- /lib/modules:/lib/modules:ro
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Because we’re on the host network, remove any `ports:` and container `sysctls:` you might have had before.
|
||||
|
||||
## Kernel parameters (on the host)
|
||||
|
||||
With host networking, system sysctls must be set on the **host**. On your host, create `/etc/sysctl.d/90-wireguard.conf`:
|
||||
|
||||
```txt
|
||||
net.ipv4.ip_forward=1
|
||||
net.ipv4.conf.all.src_valid_mark=1
|
||||
net.ipv6.conf.all.disable_ipv6=0
|
||||
net.ipv6.conf.all.forwarding=1
|
||||
net.ipv6.conf.default.forwarding=1
|
||||
```
|
||||
|
||||
Apply and verify:
|
||||
|
||||
```shell
|
||||
sysctl -p /etc/sysctl.d/90-wireguard.conf
|
||||
sysctl -n net.ipv4.ip_forward # should print 1
|
||||
```
|
||||
|
||||
## Add static routes on your router
|
||||
|
||||
Pick an IPv4 and IPv6 subnet for your clients and add static routes on your router, pointing to the WireGuard server's LAN addresses.
|
||||
|
||||
### Example
|
||||
|
||||
/// note | 2001:db8::/32
|
||||
|
||||
The _documentation prefix_ `2001:db8::/32` (RFC 3849) used in this example is not meant for production use, replace it with your own ISP-assigned IPv6 prefix (GUA) or local prefix (ULA)
|
||||
///
|
||||
|
||||
I want my WireGuard clients in `192.168.0.0/24` and `2001:db8:abc:0::/64`.
|
||||
|
||||
- Routed IPv4 subnet: `192.168.0.0/24`
|
||||
- Routed IPv6 prefix: `2001:db8:abc:0::/64`
|
||||
- WireGuard server IPs: `192.168.10.118` and `2001:db8:abc:10:216:3eff:fedb:949e`
|
||||
|
||||
On your router:
|
||||
|
||||
- Route `192.168.0.0/24` → next hop `192.168.10.118`
|
||||
- Route `2001:db8:abc:0::/64` → next hop `2001:db8:abc:10:216:3eff:fedb:949e`
|
||||
|
||||
Don't forget to create the necessary firewall rules to allow these subnets to travel across your LAN. Some routers or servers may require specific Outbound NAT rules for the chosen IPv4 and IPv6 subnets to allow traffic to traverse your LAN.
|
||||
|
||||
## `wg-easy` configuration
|
||||
|
||||
In the Web UI → Admin → Interface, click Change CIDR and set the IPv4/IPv6 routed subnets you chose above. Save.
|
||||
|
||||
Then go to Admin → Hooks and add:
|
||||
|
||||
PostUp
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
PostDown
|
||||
|
||||
```shell
|
||||
iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
/// warning | Important: When using nftables use the following hooks instead.
|
||||
|
||||
PostUp
|
||||
|
||||
```shell
|
||||
nft add chain ip filter WG_EASY; nft add rule ip filter DOCKER-USER jump WG_EASY; nft add rule ip filter WG_EASY iifname {{device}} accept; nft add rule ip filter WG_EASY oifname {{device}} accept; nft add chain ip6 filter WG_EASY; nft add rule ip6 filter DOCKER-USER jump WG_EASY; nft add rule ip6 filter WG_EASY iifname {{device}} accept; nft add rule ip6 filter WG_EASY oifname {{device}} accept;
|
||||
```
|
||||
|
||||
PostDown
|
||||
|
||||
```shell
|
||||
nft delete rule ip filter DOCKER-USER handle $(nft -a list chain ip filter DOCKER-USER | awk '/jump WG_EASY/ {print $NF}'); nft flush chain ip filter WG_EASY; nft delete chain ip filter WG_EASY; nft delete rule ip6 filter DOCKER-USER handle $(nft -a list chain ip6 filter DOCKER-USER | awk '/jump WG_EASY/ {print $NF}'); nft flush chain ip6 filter WG_EASY; nft delete chain ip6 filter WG_EASY
|
||||
```
|
||||
|
||||
///
|
||||
@@ -0,0 +1,185 @@
|
||||
---
|
||||
title: Traefik
|
||||
---
|
||||
|
||||
/// note | Opinionated
|
||||
|
||||
This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths.
|
||||
///
|
||||
|
||||
## Create docker compose project
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /etc/docker/containers/traefik
|
||||
cd /etc/docker/containers/traefik
|
||||
```
|
||||
|
||||
## Create docker compose file
|
||||
|
||||
File: `/etc/docker/containers/traefik/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:3.3
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443/tcp'
|
||||
- '443:443/udp'
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /etc/docker/volumes/traefik/traefik.yml:/traefik.yml:ro
|
||||
- /etc/docker/volumes/traefik/traefik_dynamic.yml:/traefik_dynamic.yml:ro
|
||||
- /etc/docker/volumes/traefik/acme.json:/acme.json
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
```
|
||||
|
||||
## Create traefik.yml
|
||||
|
||||
File: `/etc/docker/volumes/traefik/traefik.yml`
|
||||
|
||||
```yaml
|
||||
log:
|
||||
level: INFO
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ':80/tcp'
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
websecure:
|
||||
address: ':443/tcp'
|
||||
http:
|
||||
middlewares:
|
||||
- compress@file
|
||||
- hsts@file
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
http3: {}
|
||||
|
||||
api:
|
||||
dashboard: true
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: $mail@example.com$
|
||||
storage: acme.json
|
||||
httpChallenge:
|
||||
entryPoint: web
|
||||
|
||||
providers:
|
||||
docker:
|
||||
watch: true
|
||||
network: traefik
|
||||
exposedByDefault: false
|
||||
file:
|
||||
filename: traefik_dynamic.yml
|
||||
|
||||
serversTransport:
|
||||
insecureSkipVerify: true
|
||||
```
|
||||
|
||||
## Create traefik_dynamic.yml
|
||||
|
||||
File: `/etc/docker/volumes/traefik/traefik_dynamic.yml`
|
||||
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
services:
|
||||
basicAuth:
|
||||
users:
|
||||
- '$username$:$password$'
|
||||
compress:
|
||||
compress: {}
|
||||
hsts:
|
||||
headers:
|
||||
stsSeconds: 2592000
|
||||
routers:
|
||||
api:
|
||||
rule: Host(`traefik.$example.com$`)
|
||||
entrypoints:
|
||||
- websecure
|
||||
middlewares:
|
||||
- services
|
||||
service: api@internal
|
||||
|
||||
tls:
|
||||
options:
|
||||
default:
|
||||
cipherSuites:
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
sniStrict: true
|
||||
```
|
||||
|
||||
## Create acme.json
|
||||
|
||||
```shell
|
||||
sudo touch /etc/docker/volumes/traefik/acme.json
|
||||
sudo chmod 600 /etc/docker/volumes/traefik/acme.json
|
||||
```
|
||||
|
||||
## Create network
|
||||
|
||||
```shell
|
||||
sudo docker network create traefik
|
||||
```
|
||||
|
||||
## Start traefik
|
||||
|
||||
```shell
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
You can now access the Traefik dashboard at `https://traefik.$example.com$` with the credentials you set in `traefik_dynamic.yml`.
|
||||
|
||||
## Add Labels to `wg-easy`
|
||||
|
||||
To add labels to your `wg-easy` service, you can add the following to your `docker-compose.yml` file:
|
||||
|
||||
File: `/etc/docker/containers/wg-easy/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wg-easy:
|
||||
...
|
||||
container_name: wg-easy
|
||||
networks:
|
||||
...
|
||||
traefik: {}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.wg-easy.rule=Host(`wg-easy.$example.com$`)"
|
||||
- "traefik.http.routers.wg-easy.entrypoints=websecure"
|
||||
- "traefik.http.routers.wg-easy.service=wg-easy"
|
||||
- "traefik.http.services.wg-easy.loadbalancer.server.port=51821"
|
||||
- "traefik.docker.network=traefik"
|
||||
...
|
||||
|
||||
networks:
|
||||
...
|
||||
traefik:
|
||||
external: true
|
||||
```
|
||||
|
||||
## Restart `wg-easy`
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
You can now access `wg-easy` at `https://wg-easy.$example.com$` and start the setup.
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: FAQ
|
||||
hide:
|
||||
- navigation
|
||||
---
|
||||
|
||||
Here are some frequently asked questions or errors about `wg-easy`. If you have a question that is not answered here, please feel free to open a discussion on GitHub.
|
||||
|
||||
## Error: WireGuard exited with the error: Cannot find device "wg0"
|
||||
|
||||
This error indicates that the WireGuard interface `wg0` does not exist. This can happen if the WireGuard kernel module is not loaded or if the interface was not created properly.
|
||||
|
||||
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:
|
||||
|
||||
```shell
|
||||
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:
|
||||
|
||||
```shell
|
||||
echo "wireguard" | sudo tee -a /etc/modules
|
||||
```
|
||||
|
||||
## can't initialize iptables table `nat': Table does not exist (do you need to insmod?)
|
||||
|
||||
This error indicates that the `nat` table in `iptables` does not exist. This can happen if the `iptables` kernel module is not loaded or if the `nat` table is not supported by your kernel.
|
||||
|
||||
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:
|
||||
|
||||
```shell
|
||||
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:
|
||||
|
||||
```shell
|
||||
echo "iptable_nat" | sudo tee -a /etc/modules
|
||||
```
|
||||
|
||||
## can't initialize ip6tables table `nat': Table does not exist (do you need to insmod?)
|
||||
|
||||
This error indicates that the `nat` table in `ip6tables` does not exist. This can happen if the `ip6tables` kernel module is not loaded or if the `nat` table is not supported by your kernel.
|
||||
|
||||
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:
|
||||
|
||||
```shell
|
||||
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:
|
||||
|
||||
```shell
|
||||
echo "ip6table_nat" | sudo tee -a /etc/modules
|
||||
```
|
||||
|
||||
## can't initialize iptables table `filter': Permission denied
|
||||
|
||||
This error indicates that the `filter` table in `iptables` cannot be initialized due to permission issues. This can happen if you are not running the command with sufficient privileges.
|
||||
|
||||
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:
|
||||
|
||||
```shell
|
||||
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:
|
||||
|
||||
```shell
|
||||
echo "iptable_filter" | sudo tee -a /etc/modules
|
||||
```
|
||||
|
||||
## can't initialize ip6tables table `filter': Permission denied
|
||||
|
||||
This error indicates that the `filter` table in `ip6tables` cannot be initialized due to permission issues. This can happen if you are not running the command with sufficient privileges.
|
||||
|
||||
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:
|
||||
|
||||
```shell
|
||||
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:
|
||||
|
||||
```shell
|
||||
echo "ip6table_filter" | sudo tee -a /etc/modules
|
||||
```
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Getting Started
|
||||
hide:
|
||||
- navigation
|
||||
- navigation
|
||||
---
|
||||
|
||||
This page explains how to get started with wg-easy. The guide uses Docker Compose as a reference. In our examples, we mount the named volume `etc_wireguard` to `/etc/wireguard` inside the container.
|
||||
This page explains how to get started with `wg-easy`. The guide uses Docker Compose as a reference. In our examples, we mount the named volume `etc_wireguard` to `/etc/wireguard` inside the container.
|
||||
|
||||
## Preliminary Steps
|
||||
|
||||
@@ -29,7 +29,7 @@ If you're using podman, make sure to read the related [documentation][docs-podma
|
||||
[docker-compose]: https://docs.docker.com/compose/
|
||||
[docker-compose-installation]: https://docs.docker.com/compose/install/
|
||||
[docker-compose-specification]: https://docs.docker.com/compose/compose-file/
|
||||
[docs-podman]: ./examples/tutorials/podman.md
|
||||
[docs-podman]: ./examples/tutorials/podman-nft.md
|
||||
|
||||
## Deploying the Actual Image
|
||||
|
||||
@@ -38,55 +38,37 @@ If you're using podman, make sure to read the related [documentation][docs-podma
|
||||
To understand which tags you should use, read this section carefully. [Our CI][github-ci] will automatically build, test and push new images to the following container registry:
|
||||
|
||||
1. GitHub Container Registry ([`ghcr.io/wg-easy/wg-easy`][ghcr-image])
|
||||
2. Codeberg Container Registry ([`codeberg.org/wg-easy/wg-easy`][codeberg-image]) (IPv6 support)
|
||||
|
||||
All workflows are using the tagging convention listed below. It is subsequently applied to all images.
|
||||
|
||||
| Event | Image Tags |
|
||||
| ----------------------- | ----------------------------- |
|
||||
| `cron` on `master` | `nightly` |
|
||||
| `push` a tag (`v1.2.3`) | `1.2.3`, `1.2`, `1`, `latest` |
|
||||
| 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, recommended |
|
||||
| `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.0` | specific tag | `ghcr.io/wg-easy/wg-easy:15.0.0` | specific release, no updates |
|
||||
| `edge` | push to `master` | `ghcr.io/wg-easy/wg-easy:edge` | mostly unstable, gets frequent package and code updates |
|
||||
| `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs |
|
||||
|
||||
<!-- ref: major version (check links too) -->
|
||||
|
||||
When publishing a tag we follow the [Semantic Versioning][semver] specification. The `latest` tag is always pointing to the latest stable release. If you want to avoid breaking changes, use the major version tag (e.g. `15`).
|
||||
|
||||
[github-ci]: https://github.com/wg-easy/wg-easy/actions
|
||||
[ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy
|
||||
[codeberg-image]: https://codeberg.org/wg-easy/-/packages/container/wg-easy/15
|
||||
[semver]: https://semver.org/
|
||||
|
||||
### 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
|
||||
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
|
||||
/// danger | Use 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.
|
||||
///
|
||||
|
||||
**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
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: 2FA
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Admin Panel
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Edit Client
|
||||
---
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Setup
|
||||
---
|
||||
|
||||
## 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.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Home
|
||||
hide:
|
||||
- navigation
|
||||
- navigation
|
||||
---
|
||||
|
||||
# Welcome to the Documentation for `wg-easy`
|
||||
@@ -11,7 +11,7 @@ hide:
|
||||
**Make sure** to select the correct version of this documentation! It should match the version of the image you are using. The default version corresponds to the `:latest` image tag - [the most recent stable release][docs-tagging].
|
||||
///
|
||||
|
||||
This documentation provides you not only with the basic setup and configuration of wg-easy but also with advanced configuration, elaborate usage scenarios, detailed examples, hints and more.
|
||||
This documentation provides you not only with the basic setup and configuration of `wg-easy` but also with advanced configuration, elaborate usage scenarios, detailed examples, hints and more.
|
||||
|
||||
[docs-tagging]: ./getting-started.md#tagging-convention
|
||||
|
||||
|
||||
+73
-73
@@ -1,87 +1,87 @@
|
||||
site_name: "wg-easy"
|
||||
site_description: "The easiest way to run WireGuard VPN + Web-based Admin UI."
|
||||
site_author: "WireGuard Easy"
|
||||
site_name: 'wg-easy'
|
||||
site_description: 'The easiest way to run WireGuard VPN + Web-based Admin UI.'
|
||||
site_author: 'WireGuard Easy'
|
||||
copyright: >
|
||||
<p>
|
||||
© <a href="https://github.com/wg-easy"><em>Wireguard Easy</em></a><br/>
|
||||
<span>This project is licensed under AGPL-3.0-only.</span><br/>
|
||||
<span>This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security</span><br/>
|
||||
<span>"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld</span>
|
||||
</p>
|
||||
<p>
|
||||
© <a href="https://github.com/wg-easy"><em>Wireguard Easy</em></a><br/>
|
||||
<span>This project is licensed under AGPL-3.0-only.</span><br/>
|
||||
<span>This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security</span><br/>
|
||||
<span>"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld</span>
|
||||
</p>
|
||||
|
||||
repo_url: https://github.com/wg-easy/wg-easy
|
||||
repo_name: wg-easy
|
||||
|
||||
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
|
||||
|
||||
theme:
|
||||
name: material
|
||||
favicon: assets/logo/favicon.png
|
||||
logo: assets/logo/logo.png
|
||||
icon:
|
||||
repo: fontawesome/brands/github
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.top
|
||||
- navigation.expand
|
||||
- navigation.instant
|
||||
- content.action.edit
|
||||
- content.action.view
|
||||
- content.code.annotate
|
||||
palette:
|
||||
# Light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: grey
|
||||
accent: red
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
# Dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
primary: grey
|
||||
accent: red
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
name: material
|
||||
favicon: assets/logo/favicon.png
|
||||
logo: assets/logo/logo.png
|
||||
icon:
|
||||
repo: fontawesome/brands/github
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.top
|
||||
- navigation.expand
|
||||
- navigation.instant
|
||||
- content.action.edit
|
||||
- content.action.view
|
||||
- content.code.annotate
|
||||
palette:
|
||||
# Light mode
|
||||
- media: '(prefers-color-scheme: light)'
|
||||
scheme: default
|
||||
primary: grey
|
||||
accent: red
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
# Dark mode
|
||||
- media: '(prefers-color-scheme: dark)'
|
||||
scheme: slate
|
||||
primary: grey
|
||||
accent: red
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
|
||||
extra:
|
||||
version:
|
||||
provider: mike
|
||||
version:
|
||||
provider: mike
|
||||
|
||||
markdown_extensions:
|
||||
- toc:
|
||||
anchorlink: true
|
||||
- abbr
|
||||
- attr_list
|
||||
- pymdownx.blocks.admonition:
|
||||
types:
|
||||
- danger
|
||||
- note
|
||||
- info
|
||||
- question
|
||||
- warning
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
slugify: !!python/object/apply:pymdownx.slugs.slugify
|
||||
kwds:
|
||||
case: lower
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.magiclink
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.tilde
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
- toc:
|
||||
anchorlink: true
|
||||
- abbr
|
||||
- attr_list
|
||||
- pymdownx.blocks.admonition:
|
||||
types:
|
||||
- danger
|
||||
- note
|
||||
- info
|
||||
- question
|
||||
- warning
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
slugify: !!python/object/apply:pymdownx.slugs.slugify
|
||||
kwds:
|
||||
case: lower
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.magiclink
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.tilde
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
|
||||
+10
-3
@@ -2,9 +2,16 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"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 .",
|
||||
"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:i18n": "bash scripts/i18n.sh",
|
||||
"format:check:docs": "prettier --check docs"
|
||||
},
|
||||
"packageManager": "pnpm@10.5.2"
|
||||
"devDependencies": {
|
||||
"prettier": "^3.7.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.28.0"
|
||||
}
|
||||
|
||||
Generated
+16
-1
@@ -6,4 +6,19 @@ settings:
|
||||
|
||||
importers:
|
||||
|
||||
.: {}
|
||||
.:
|
||||
devDependencies:
|
||||
prettier:
|
||||
specifier: ^3.7.4
|
||||
version: 3.7.4
|
||||
|
||||
packages:
|
||||
|
||||
prettier@3.7.4:
|
||||
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
prettier@3.7.4: {}
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
folder="src/i18n/locales"
|
||||
base_file="$folder/en.json"
|
||||
|
||||
# Get all leaf keys from the English base file
|
||||
base_keys=$(jq -r 'paths(scalars) | map(tostring) | join(".")' "$base_file")
|
||||
total=$(echo "$base_keys" | wc -l)
|
||||
|
||||
# Loop through all JSON files in the folder
|
||||
for file in "$folder"/*.json; do
|
||||
name=$(basename "$file" .json)
|
||||
translated_keys=$(jq -r 'paths(scalars) | map(tostring) | join(".")' "$file")
|
||||
done=$(comm -12 <(echo "$base_keys" | sort) <(echo "$translated_keys" | sort) | wc -l)
|
||||
percent=$((100 * done / total))
|
||||
check="[ ]"
|
||||
[ "$percent" -eq 100 ] && check="[x]"
|
||||
printf "%s %s (%d%%)\n" "- $check" "$name" "$percent"
|
||||
done
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
|
||||
package_json="src/package.json"
|
||||
|
||||
# Function to update the version in package.json
|
||||
update_version() {
|
||||
local new_version=$1
|
||||
jq --arg new_version "$new_version" '.version = $new_version' $package_json > tmp.json && mv tmp.json $package_json
|
||||
}
|
||||
|
||||
# Get the current version from package.json
|
||||
current_version=$(jq -r '.version' $package_json)
|
||||
echo "Current version: $current_version"
|
||||
|
||||
# Prompt the user for the new version
|
||||
read -p "Enter the new version (following SemVer): " new_version
|
||||
|
||||
# Official SemVer regex for validation
|
||||
semver_regex="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||
|
||||
# Validate the new version
|
||||
if ! echo "$new_version" | grep -Eq "$semver_regex"; then
|
||||
echo "Invalid version format. Please use SemVer format (e.g., 1.0.0 or 1.0.0-alpha)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update the version in package.json
|
||||
update_version $new_version
|
||||
echo "Updated package.json to version $new_version"
|
||||
|
||||
echo "----"
|
||||
echo "If you changed the major version, remember to update the docker-compose.yml file and docs (search for: ref: major version)"
|
||||
echo "----"
|
||||
|
||||
echo "If you did everything press 'y' to commit the changes and create a new tag"
|
||||
read -p "Do you want to continue? (y/n): " confirm
|
||||
|
||||
if [ "$confirm" != "y" ]; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Commit the changes
|
||||
git add $package_json
|
||||
git commit -m "Bump version to $new_version"
|
||||
echo "Committed the changes"
|
||||
|
||||
# Create a new Git tag
|
||||
git tag -a "v$new_version" -m "Release version $new_version"
|
||||
echo "Created Git tag v$new_version"
|
||||
|
||||
# Push the commit & tag to the remote repository
|
||||
git push origin master --follow-tags
|
||||
echo "Pushed Git commit and tag v$new_version to remote repository"
|
||||
@@ -1 +0,0 @@
|
||||
public-hoist-pattern[]=@libsql/linux*
|
||||
@@ -10,12 +10,12 @@
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose as-child>
|
||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
||||
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
<DialogClose as-child>
|
||||
<BaseButton @click="$emit('change', ipv4Cidr, ipv6Cidr)">
|
||||
<BasePrimaryButton @click="$emit('change', ipv4Cidr, ipv6Cidr)">
|
||||
{{ $t('dialog.change') }}
|
||||
</BaseButton>
|
||||
</BasePrimaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<BaseDialog :trigger-class="triggerClass">
|
||||
<template #trigger><slot /></template>
|
||||
<template #title>{{ $t('admin.interface.restart') }}</template>
|
||||
<template #description>
|
||||
{{ $t('admin.interface.restartWarn') }}
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose as-child>
|
||||
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
<DialogClose as-child>
|
||||
<BasePrimaryButton @click="$emit('restart')">
|
||||
{{ $t('admin.interface.restart') }}
|
||||
</BasePrimaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineEmits(['restart']);
|
||||
defineProps<{ triggerClass?: string }>();
|
||||
</script>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<BaseDialog :trigger-class="triggerClass">
|
||||
<template #trigger><slot /></template>
|
||||
<template #title>{{ $t('admin.config.suggest') }}</template>
|
||||
<template #description>
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<p>{{ $t('admin.config.suggestDesc') }}</p>
|
||||
<p v-if="!data">
|
||||
{{ $t('general.loading') }}
|
||||
</p>
|
||||
<BaseSelect v-else v-model="selected" :options="data" />
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose as-child>
|
||||
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
<DialogClose as-child>
|
||||
<BasePrimaryButton @click="$emit('change', selected)">
|
||||
{{ $t('dialog.change') }}
|
||||
</BasePrimaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineEmits(['change']);
|
||||
const props = defineProps<{
|
||||
triggerClass?: string;
|
||||
url: '/api/admin/ip-info' | '/api/setup/4';
|
||||
}>();
|
||||
|
||||
const { data } = useFetch(props.url, {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
const selected = ref<string>();
|
||||
</script>
|
||||
@@ -11,10 +11,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { VueApexChartsComponent } from 'vue3-apexcharts';
|
||||
import type { VueApexChartsComponentProps } from 'vue3-apexcharts';
|
||||
|
||||
defineProps<{
|
||||
options: VueApexChartsComponent['options'];
|
||||
series: VueApexChartsComponent['series'];
|
||||
options: VueApexChartsComponentProps['options'];
|
||||
series: VueApexChartsComponentProps['series'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="overflow-x-auto rounded border-2 border-red-800 py-2">
|
||||
<pre
|
||||
class="mx-2 inline-block"
|
||||
@click="selectCode"
|
||||
><code ref="codeBlock">{{ code }}</code></pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
code: string;
|
||||
}>();
|
||||
|
||||
const codeBlock = useTemplateRef('codeBlock');
|
||||
|
||||
function selectCode() {
|
||||
// TODO: keyboard support?
|
||||
if (codeBlock.value) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(codeBlock.value);
|
||||
const sel = window.getSelection();
|
||||
if (sel) {
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -6,7 +6,7 @@
|
||||
class="fixed inset-0 z-30 bg-gray-500 opacity-75 dark:bg-black dark:opacity-50"
|
||||
/>
|
||||
<DialogContent
|
||||
class="fixed left-1/2 top-1/2 z-[100] max-h-[85vh] w-[90vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md p-6 shadow-2xl focus:outline-none dark:bg-neutral-700"
|
||||
class="fixed left-1/2 top-1/2 z-[100] max-h-[85vh] w-[90vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md bg-white p-6 shadow-2xl focus:outline-none dark:bg-neutral-700"
|
||||
>
|
||||
<DialogTitle
|
||||
class="m-0 text-lg font-semibold text-gray-900 dark:text-neutral-200"
|
||||
|
||||
@@ -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 attrs = computed(() => {
|
||||
const { as, ...attrs } = props;
|
||||
return attrs;
|
||||
const { as, ...rest } = props;
|
||||
return rest;
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<SelectRoot v-model="selected">
|
||||
<SelectTrigger
|
||||
class="inline-flex h-8 items-center justify-around gap-2 rounded bg-gray-200 px-3 text-sm leading-none dark:bg-neutral-500 dark:text-neutral-200"
|
||||
aria-label="Choose option"
|
||||
>
|
||||
<SelectValue placeholder="Select..." />
|
||||
<IconsArrowDown class="size-3" />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
class="z-[100] min-w-28 rounded bg-gray-300 dark:bg-neutral-500"
|
||||
>
|
||||
<SelectViewport class="p-2">
|
||||
<SelectItem
|
||||
v-for="(option, index) in options"
|
||||
:key="index"
|
||||
:value="option.value"
|
||||
class="relative flex h-6 items-center rounded px-3 text-sm leading-none outline-none hover:bg-red-800 hover:text-white dark:text-white"
|
||||
>
|
||||
<SelectItemText>
|
||||
{{ option.value }} - {{ option.label }}
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</SelectViewport>
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</SelectRoot>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
options: { label: string; value: string }[];
|
||||
}>();
|
||||
const selected = defineModel<string>();
|
||||
</script>
|
||||
@@ -1,14 +1,17 @@
|
||||
<template>
|
||||
<TooltipProvider>
|
||||
<TooltipRoot>
|
||||
<TooltipRoot :open="open" @update:open="open = $event">
|
||||
<TooltipTrigger
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-full text-gray-400 outline-none focus:shadow-sm focus:shadow-black"
|
||||
class="mx-2 inline-flex h-4 w-4 items-center justify-center rounded-full text-gray-400 outline-none focus:shadow-sm focus:shadow-black"
|
||||
as-child
|
||||
>
|
||||
<slot />
|
||||
<button type="button" @click="open = !open">
|
||||
<slot />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent
|
||||
class="select-none rounded bg-gray-600 px-3 py-2 text-sm leading-none text-white shadow-lg will-change-[transform,opacity]"
|
||||
class="select-none whitespace-pre-line rounded bg-gray-600 px-3 py-2 text-sm leading-none text-white shadow-lg will-change-[transform,opacity]"
|
||||
:side-offset="5"
|
||||
>
|
||||
{{ text }}
|
||||
@@ -21,4 +24,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ text: string }>();
|
||||
|
||||
const open = ref(false);
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<BaseDialog :trigger-class="triggerClass">
|
||||
<template #trigger>
|
||||
<slot />
|
||||
</template>
|
||||
<template #title>
|
||||
{{ $t('client.config') }}
|
||||
</template>
|
||||
<template #description>
|
||||
<div v-if="status === 'success'">
|
||||
<BaseCodeBlock :code="config ?? ''" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>{{ $t('general.loading') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose as-child>
|
||||
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
<DialogClose as-child>
|
||||
<BasePrimaryButton @click="copyCode">
|
||||
{{ $t('copy.copy') }}
|
||||
</BasePrimaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ triggerClass?: string; clientId: number }>();
|
||||
|
||||
const toast = useToast();
|
||||
const { copied, copy, isSupported } = useClipboard({
|
||||
// fallback does not work
|
||||
legacy: false,
|
||||
});
|
||||
|
||||
const { data: config, status } = useFetch(
|
||||
`/api/client/${props.clientId}/configuration`,
|
||||
{
|
||||
responseType: 'text',
|
||||
server: false,
|
||||
}
|
||||
);
|
||||
|
||||
async function copyCode() {
|
||||
if (status.value !== 'success') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSupported.value) {
|
||||
toast.showToast({
|
||||
type: 'error',
|
||||
message: $t('copy.notSupported'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await copy(config.value ?? '');
|
||||
|
||||
if (copied.value) {
|
||||
toast.showToast({
|
||||
type: 'success',
|
||||
message: $t('copy.copied'),
|
||||
});
|
||||
} else {
|
||||
toast.showToast({
|
||||
type: 'error',
|
||||
message: $t('copy.failed'),
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -18,10 +18,12 @@
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose as-child>
|
||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
||||
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
<DialogClose as-child>
|
||||
<BaseButton @click="createClient">{{ $t('client.create') }}</BaseButton>
|
||||
<BasePrimaryButton @click="createClient">
|
||||
{{ $t('client.create') }}
|
||||
</BasePrimaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose as-child>
|
||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
||||
<BasePrimaryButton>{{ $t('dialog.cancel') }}</BasePrimaryButton>
|
||||
</DialogClose>
|
||||
<DialogClose as-child>
|
||||
<BaseButton @click="$emit('delete')">{{
|
||||
$t('client.deleteClient')
|
||||
}}</BaseButton>
|
||||
<BaseSecondaryButton @click="$emit('delete')">
|
||||
{{ $t('client.deleteClient') }}
|
||||
</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<p class="m-10 text-center text-sm text-gray-400 dark:text-neutral-400">
|
||||
{{ $t('client.empty') }}<br /><br />
|
||||
<ClientsCreateDialog>
|
||||
<BaseButton as="span">
|
||||
<BaseSecondaryButton as="span">
|
||||
<IconsPlus class="w-4 md:mr-2" />
|
||||
<span class="text-sm">{{ $t('client.new') }}</span>
|
||||
</BaseButton>
|
||||
</BaseSecondaryButton>
|
||||
</ClientsCreateDialog>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<ClientsCreateDialog>
|
||||
<BaseButton as="span">
|
||||
<BaseSecondaryButton as="span">
|
||||
<IconsPlus class="w-4 md:mr-2" />
|
||||
<span class="text-sm max-md:hidden">{{ $t('client.newShort') }}</span>
|
||||
</BaseButton>
|
||||
</BaseSecondaryButton>
|
||||
</ClientsCreateDialog>
|
||||
</template>
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
<slot />
|
||||
</template>
|
||||
<template #description>
|
||||
<img :src="qrCode" />
|
||||
<div class="bg-white">
|
||||
<img :src="qrCode" />
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose>
|
||||
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
||||
<DialogClose as-child>
|
||||
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="relative w-60 md:mr-2">
|
||||
<div class="relative flex h-full items-center">
|
||||
<MagnifyingGlassIcon
|
||||
class="absolute left-2.5 h-4 w-4 text-gray-400 dark:text-neutral-500"
|
||||
/>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
:placeholder="$t('client.search')"
|
||||
class="w-full rounded bg-white py-2 pr-8 text-sm text-gray-900 shadow-sm ring-1 ring-gray-300 transition-all placeholder:text-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-red-600 dark:bg-neutral-800 dark:text-white dark:ring-neutral-700 dark:placeholder:text-neutral-500 dark:focus:ring-red-700"
|
||||
@input="updateSearch"
|
||||
/>
|
||||
<button
|
||||
v-if="searchQuery"
|
||||
class="absolute right-2 flex h-5 w-5 items-center justify-center rounded-full bg-gray-200 text-gray-600 hover:bg-gray-300 hover:text-gray-800 dark:bg-neutral-700 dark:text-neutral-300 dark:hover:bg-neutral-600 dark:hover:text-neutral-100"
|
||||
aria-label="Clear search"
|
||||
@click="clearSearch"
|
||||
>
|
||||
<IconsClose class="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const clientsStore = useClientsStore();
|
||||
const searchQuery = ref('');
|
||||
|
||||
const updateSearch = useDebounceFn(() => {
|
||||
clientsStore.setSearchQuery(searchQuery.value);
|
||||
}, 300);
|
||||
|
||||
function clearSearch() {
|
||||
searchQuery.value = '';
|
||||
clientsStore.setSearchQuery('');
|
||||
}
|
||||
</script>
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<BaseButton @click="toggleSort">
|
||||
<BasePrimaryButton @click="toggleSort">
|
||||
<IconsArrowDown
|
||||
v-if="globalStore.sortClient === true"
|
||||
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>
|
||||
</BaseButton>
|
||||
</BasePrimaryButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div v-if="data?.length === 0">
|
||||
{{ emptyText || $t('form.noItems') }}
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-2">
|
||||
<div v-for="(item, i) in data" :key="i">
|
||||
<div class="flex flex-row gap-1">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-if="data?.length === 0">
|
||||
{{ emptyText || $t('form.noItems') }}
|
||||
</div>
|
||||
<div v-for="(item, i) in data" v-else :key="i">
|
||||
<div class="mt-1 flex flex-row gap-1">
|
||||
<input
|
||||
:value="item"
|
||||
:name="name"
|
||||
@@ -12,13 +12,20 @@
|
||||
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
|
||||
@input="update($event, i)"
|
||||
/>
|
||||
<BaseButton as="input" type="button" value="-" @click="del(i)" />
|
||||
<BaseSecondaryButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
value="-"
|
||||
@click="del(i)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<BaseButton
|
||||
<BasePrimaryButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
:value="$t('form.add')"
|
||||
@click="add"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<section class="grid grid-cols-2 gap-4">
|
||||
<slot />
|
||||
<Separator
|
||||
decorative
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<FormLabel :for="id">
|
||||
{{ label }}
|
||||
</FormLabel>
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<BaseInput
|
||||
:id="id"
|
||||
v-model.trim="data"
|
||||
:name="id"
|
||||
type="text"
|
||||
class="w-full"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<ClientOnly>
|
||||
<AdminSuggestDialog :url="url" @change="data = $event">
|
||||
<BasePrimaryButton as="span">
|
||||
<div class="flex items-center gap-3">
|
||||
<IconsSparkles class="w-4" />
|
||||
<span class="whitespace-nowrap">
|
||||
{{ $t('admin.config.suggest') }}
|
||||
</span>
|
||||
</div>
|
||||
</BasePrimaryButton>
|
||||
</AdminSuggestDialog>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
url: '/api/admin/ip-info' | '/api/setup/4';
|
||||
}>();
|
||||
|
||||
const data = defineModel<string | null>({
|
||||
set(value) {
|
||||
const temp = value?.trim() ?? null;
|
||||
if (temp === '') {
|
||||
return null;
|
||||
}
|
||||
return temp;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<FormLabel :for="id">
|
||||
{{ label }}
|
||||
</FormLabel>
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<span :id="id" class="flex flex-col justify-center">{{ data }}</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
data?: string;
|
||||
}>();
|
||||
</script>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-if="data === null">
|
||||
{{ emptyText || $t('form.nullNoItems') }}
|
||||
</div>
|
||||
<div v-for="(item, i) in data" v-else :key="i">
|
||||
<div class="mt-1 flex flex-row gap-1">
|
||||
<input
|
||||
:value="item"
|
||||
:name="name"
|
||||
type="text"
|
||||
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
|
||||
@input="update($event, i)"
|
||||
/>
|
||||
<BaseSecondaryButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
value="-"
|
||||
@click="del(i)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<BasePrimaryButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
:value="$t('form.add')"
|
||||
@click="add"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const data = defineModel<string[] | null>();
|
||||
defineProps<{ emptyText?: string[]; name: string }>();
|
||||
|
||||
function update(e: Event, i: number) {
|
||||
const v = (e.target as HTMLInputElement).value;
|
||||
if (!data.value) {
|
||||
return;
|
||||
}
|
||||
data.value[i] = v;
|
||||
}
|
||||
|
||||
function add() {
|
||||
if (data.value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (data.value === null) {
|
||||
data.value = [''];
|
||||
} else {
|
||||
data.value.push('');
|
||||
}
|
||||
}
|
||||
|
||||
function del(i: number) {
|
||||
if (!data.value) {
|
||||
return;
|
||||
}
|
||||
data.value.splice(i, 1);
|
||||
if (data.value.length === 0) {
|
||||
data.value = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<FormLabel :for="id">
|
||||
{{ label }}
|
||||
</FormLabel>
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<BaseInput :id="id" v-model.number="data" :name="id" type="number" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ id: string; label: string; description?: string }>();
|
||||
|
||||
const data = defineModel<number | null>({
|
||||
set(value) {
|
||||
const temp = value ?? null;
|
||||
if (temp === 0) {
|
||||
return null;
|
||||
}
|
||||
if ((temp as string | null) === '') {
|
||||
return null;
|
||||
}
|
||||
return temp;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -12,7 +12,7 @@
|
||||
v-model.trim="data"
|
||||
:name="id"
|
||||
type="text"
|
||||
:autcomplete="autocomplete"
|
||||
:autocomplete="autocomplete"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -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>
|
||||
@@ -12,7 +12,8 @@
|
||||
v-model.trim="data"
|
||||
:name="id"
|
||||
type="text"
|
||||
:autcomplete="autocomplete"
|
||||
:autocomplete="autocomplete"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -22,6 +23,7 @@ defineProps<{
|
||||
label: string;
|
||||
description?: string;
|
||||
autocomplete?: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
const data = defineModel<string>();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="!globalStore.information?.insecure && !https"
|
||||
class="container mx-auto w-fit rounded-md bg-red-800 p-4 text-white shadow-lg dark:bg-red-100 dark:text-red-600"
|
||||
>
|
||||
<p class="text-center">{{ $t('login.insecure') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const globalStore = useGlobalStore();
|
||||
|
||||
const https = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
if (window.location.protocol === 'https:') {
|
||||
https.value = true;
|
||||
} else {
|
||||
https.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<NuxtLink to="/" class="mb-4 flex-grow self-start">
|
||||
<NuxtLink to="/" class="mb-4">
|
||||
<h1 class="text-4xl font-medium dark:text-neutral-200">
|
||||
<img
|
||||
src="/logo.png"
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="globalStore.release?.updateAvailable"
|
||||
v-if="
|
||||
globalStore.information?.updateAvailable &&
|
||||
authStore.userData &&
|
||||
hasPermissions(authStore.userData, 'admin', 'any')
|
||||
"
|
||||
class="font-small mb-10 rounded-md bg-red-800 p-4 text-sm text-white shadow-lg dark:bg-red-100 dark:text-red-600"
|
||||
:title="`v${globalStore.release.currentRelease} → v${globalStore.release.latestRelease.version}`"
|
||||
:title="`v${globalStore.information.currentRelease} → v${globalStore.information.latestRelease.version}`"
|
||||
>
|
||||
<div class="container mx-auto flex flex-auto flex-row items-center">
|
||||
<div class="flex-grow">
|
||||
<p class="font-bold">{{ $t('update.updateAvailable') }}</p>
|
||||
<p>{{ globalStore.release.latestRelease.changelog }}</p>
|
||||
<p>{{ globalStore.information.latestRelease.changelog }}</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
:href="`https://github.com/wg-easy/wg-easy/releases/tag/${globalStore.release.latestRelease.version}`"
|
||||
:href="`https://github.com/wg-easy/wg-easy/releases/tag/${globalStore.information.latestRelease.version}`"
|
||||
target="_blank"
|
||||
class="font-sm float-right flex-shrink-0 rounded-md border-2 border-red-800 bg-white p-3 font-semibold text-red-800 transition-all hover:border-white hover:bg-red-800 hover:text-white dark:border-red-600 dark:bg-red-100 dark:text-red-600 dark:hover:border-red-600 dark:hover:bg-red-600 dark:hover:text-red-100"
|
||||
>
|
||||
@@ -23,6 +27,5 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
const globalStore = useGlobalStore();
|
||||
|
||||
// TODO: only show this to admins
|
||||
const authStore = useAuthStore();
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowDownIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ArrowDownIcon from '@heroicons/vue/24/outline/esm/ArrowDownIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
inline
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowPathIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ArrowPathIcon from '@heroicons/vue/24/outline/esm/ArrowPathIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m11.25 9-3 3m0 0 3 3m-3-3h7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowLeftCircleIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ArrowLeftCircleIcon from '@heroicons/vue/24/outline/esm/ArrowLeftCircleIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowRightCircleIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ArrowRightCircleIcon from '@heroicons/vue/24/outline/esm/ArrowLeftCircleIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowUpIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ArrowUpIcon from '@heroicons/vue/24/outline/esm/ArrowUpIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z"
|
||||
/>
|
||||
</svg>
|
||||
<ChartBarIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ChartBarIcon from '@heroicons/vue/24/outline/esm/ChartBarIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
<CheckCircleIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import CheckCircleIcon from '@heroicons/vue/24/outline/esm/CheckCircleIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<XMarkIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import XMarkIcon from '@heroicons/vue/24/outline/esm/XMarkIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<TrashIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TrashIcon from '@heroicons/vue/24/outline/esm/TrashIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowDownTrayIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ArrowDownTrayIcon from '@heroicons/vue/24/outline/esm/ArrowDownTrayIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
<PencilSquareIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PencilSquareIcon from '@heroicons/vue/24/outline/esm/PencilSquareIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
<InformationCircleIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import InformationCircleIcon from '@heroicons/vue/24/outline/esm/InformationCircleIcon';
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802"
|
||||
/>
|
||||
</svg>
|
||||
<LanguageIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import LanguageIcon from '@heroicons/vue/24/outline/esm/LanguageIcon';
|
||||
</script>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user