Compare commits
239
Commits
v15.0.0
...
e7c91c34f2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7c91c34f2 | ||
|
|
94a9967dd1 | ||
|
|
f731135564 | ||
|
|
3dfea018c3 | ||
|
|
2adadfb582 | ||
|
|
d4a088b951 | ||
|
|
1bc0529e63 | ||
|
|
5fed90e0f8 | ||
|
|
de008e5542 | ||
|
|
c50e99cd90 | ||
|
|
0b97cf6d23 | ||
|
|
52fb584bb0 | ||
|
|
8ae33866fc | ||
|
|
78b2838498 | ||
|
|
915e559514 | ||
|
|
ee232569f0 | ||
|
|
11071c2ad5 | ||
|
|
ec301d17d7 | ||
|
|
977a18801b | ||
|
|
391bed2256 | ||
|
|
042dce50b5 | ||
|
|
e158d073c2 | ||
|
|
c7b665b61c | ||
|
|
2af5cd04b4 | ||
|
|
e7ea65a898 | ||
|
|
032347648d | ||
|
|
5f54fa3e58 | ||
|
|
30498520d2 | ||
|
|
47c6f476ac | ||
|
|
09cf208933 | ||
|
|
e9252be45c | ||
|
|
66b292b11b | ||
|
|
bb5aff1919 | ||
|
|
8add67a4cc | ||
|
|
ac547bbf7c | ||
|
|
8c70c24205 | ||
|
|
c70ad1d08b | ||
|
|
d0566a1df9 | ||
|
|
bc95a2851f | ||
|
|
e03d743307 | ||
|
|
99357848e5 | ||
|
|
c41ae0d4c5 | ||
|
|
66f8bde206 | ||
|
|
b3afb9ac1b | ||
|
|
9581e6eacb | ||
|
|
90e2bbe0a6 | ||
|
|
7b5ba95938 | ||
|
|
da90d67cc0 | ||
|
|
a52da67b38 | ||
|
|
e513090074 | ||
|
|
2dc8ba7792 | ||
|
|
4e8cccb4c7 | ||
|
|
e57b0977d3 | ||
|
|
b8be53c3f7 | ||
|
|
0794413191 | ||
|
|
261b0d6b8f | ||
|
|
f656d57d20 | ||
|
|
46074fea1c | ||
|
|
05c655ede9 | ||
|
|
ebcc42cc49 | ||
|
|
be8d24e492 | ||
|
|
9682dedea7 | ||
|
|
5eb80fe3c1 | ||
|
|
dd9da2a067 | ||
|
|
15111ecd62 | ||
|
|
e9f4b4650b | ||
|
|
e3e4049f8e | ||
|
|
3fb9adbf6f | ||
|
|
cd9db1563d | ||
|
|
b5c30f5dbe | ||
|
|
1eb9527175 | ||
|
|
cd890c1f0f | ||
|
|
2a78b30aeb | ||
|
|
9a843087c3 | ||
|
|
483b63bba6 | ||
|
|
13942c97b2 | ||
|
|
82c64e506e | ||
|
|
9b3d919168 | ||
|
|
3eaf0d01dc | ||
|
|
414e9a114b | ||
|
|
2d28d87c5c | ||
|
|
76c2233e46 | ||
|
|
abedf9f38e | ||
|
|
25f3fa3c0f | ||
|
|
c3c51f8088 | ||
|
|
8ea2b635c1 | ||
|
|
bc4dfd03df | ||
|
|
7cde04de81 | ||
|
|
5228734c98 | ||
|
|
47f81dd66a | ||
|
|
e5b2c3d10b | ||
|
|
059a0ccffc | ||
|
|
8c9c54c8b2 | ||
|
|
02ce6f0a65 | ||
|
|
48682e1abd | ||
|
|
044dd34dcc | ||
|
|
a469ac6897 | ||
|
|
1178d23659 | ||
|
|
b3cc1ce839 | ||
|
|
71aaec93ef | ||
|
|
7a219b73d4 | ||
|
|
c456c5e7dd | ||
|
|
a5880cc0b8 | ||
|
|
5fca628ebd | ||
|
|
7ab297c366 | ||
|
|
c5de8f0f44 | ||
|
|
c0641889cf | ||
|
|
9141562f91 | ||
|
|
d21af70df1 | ||
|
|
56ee86cc1c | ||
|
|
f017b4968c | ||
|
|
6004457666 | ||
|
|
1a5a0180ea | ||
|
|
c732f149e6 | ||
|
|
4819480eb0 | ||
|
|
fc7ab0dc21 | ||
|
|
eb6b96c0f1 | ||
|
|
f62fad9c40 | ||
|
|
e9a472c8f7 | ||
|
|
552e2b8cbf | ||
|
|
a0b4192cbd | ||
|
|
32a055093a | ||
|
|
51558c7027 | ||
|
|
b85286f0ab | ||
|
|
48f3fbd715 | ||
|
|
458f66818a | ||
|
|
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 |
@@ -1,28 +1,51 @@
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
<!-- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
<!-- Describe your changes in detail -->
|
||||
<!-- Focus on the implementation and any important technical decisions. -->
|
||||
|
||||
## Motivation and Context
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
<!-- Why is this change needed? What problem does it solve? -->
|
||||
<!-- Use "Fixes #123" or "Closes #123" when this pull request resolves an issue. -->
|
||||
|
||||
## How has this been tested?
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, tests ran to see how -->
|
||||
<!--- your change affects other areas of the code, etc. -->
|
||||
|
||||
## Screenshots (if appropriate):
|
||||
<!-- Please describe in detail how you tested your changes. -->
|
||||
<!-- Include details of your testing environment, tests ran to see how -->
|
||||
<!-- your change affects other areas of the code, etc. -->
|
||||
|
||||
## Screenshots:
|
||||
|
||||
<!-- Add screenshots or recordings for visual changes. -->
|
||||
<!-- Remove this section if it is not applicable. -->
|
||||
|
||||
## Types of changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
|
||||
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Refactoring (change which does not alter existing functionality)
|
||||
- [ ] Documentation (changes to documentation only)
|
||||
|
||||
## Checklist:
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
<!-- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] I have reviewed my own changes.
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] I have added or updated tests where appropriate.
|
||||
- [ ] My changes do not include unrelated modifications.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
|
||||
## AI Disclosure:
|
||||
|
||||
<!-- See the AI contribution policy: https://github.com/wg-easy/wg-easy/blob/master/contributing.md#ai-contribution-policy -->
|
||||
<!-- Disclose whether AI tools were used -->
|
||||
<!-- Which tools were used -->
|
||||
<!-- The extent to which they were used -->
|
||||
|
||||
@@ -5,13 +5,3 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
rebase-strategy: "auto"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
rebase-strategy: "auto"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/src/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
rebase-strategy: "auto"
|
||||
|
||||
@@ -27,17 +27,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- 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}}"
|
||||
|
||||
@@ -18,10 +18,10 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
os: ubuntu-24.04-arm
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
# - platform: linux/arm/v7
|
||||
# os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
@@ -38,21 +38,21 @@ jobs:
|
||||
latest=false
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.arch.platform }}
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -85,28 +85,36 @@ jobs:
|
||||
needs: docker-build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Codeberg
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: codeberg.org
|
||||
username: ${{ secrets.CODEBERG_USER }}
|
||||
password: ${{ secrets.CODEBERG_PASS }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
codeberg.org/wg-easy/wg-easy
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
@@ -115,8 +123,14 @@ jobs:
|
||||
- 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 ' *)
|
||||
n=3
|
||||
for i in $(seq 1 $n); do
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *) && break
|
||||
|
||||
[ "$i" = "$n" ] && exit 1
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
@@ -130,10 +144,10 @@ jobs:
|
||||
contents: write
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11.9
|
||||
cache: "pip"
|
||||
|
||||
@@ -25,10 +25,10 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
os: ubuntu-24.04-arm
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
# - platform: linux/arm/v7
|
||||
# os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: master
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
@@ -47,21 +47,21 @@ jobs:
|
||||
latest=false
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.arch.platform }}
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -94,28 +94,36 @@ jobs:
|
||||
needs: docker-build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Codeberg
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: codeberg.org
|
||||
username: ${{ secrets.CODEBERG_USER }}
|
||||
password: ${{ secrets.CODEBERG_PASS }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
codeberg.org/wg-easy/wg-easy
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
@@ -124,8 +132,14 @@ jobs:
|
||||
- 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 ' *)
|
||||
n=3
|
||||
for i in $(seq 1 $n); do
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *) && break
|
||||
|
||||
[ "$i" = "$n" ] && exit 1
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
@@ -139,12 +153,12 @@ jobs:
|
||||
contents: write
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11.9
|
||||
cache: "pip"
|
||||
|
||||
@@ -21,10 +21,10 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
os: ubuntu-24.04-arm
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
# - platform: linux/arm/v7
|
||||
# os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -32,20 +32,20 @@ jobs:
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
|
||||
@@ -26,10 +26,10 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
os: ubuntu-24.04-arm
|
||||
- platform: linux/arm/v7
|
||||
os: ubuntu-24.04-arm
|
||||
# - platform: linux/arm/v7
|
||||
# os: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
@@ -46,21 +46,21 @@ jobs:
|
||||
latest=false
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.arch.platform }}
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -95,28 +95,36 @@ jobs:
|
||||
needs: docker-build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Codeberg
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: codeberg.org
|
||||
username: ${{ secrets.CODEBERG_USER }}
|
||||
password: ${{ secrets.CODEBERG_PASS }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
codeberg.org/wg-easy/wg-easy
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
@@ -127,8 +135,14 @@ jobs:
|
||||
- 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 ' *)
|
||||
n=3
|
||||
for i in $(seq 1 $n); do
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *) && break
|
||||
|
||||
[ "$i" = "$n" ] && exit 1
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
@@ -144,10 +158,10 @@ jobs:
|
||||
contents: write
|
||||
needs: docker-merge
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11.9
|
||||
cache: "pip"
|
||||
|
||||
@@ -14,17 +14,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
node-version: "lts/krypton"
|
||||
check-latest: true
|
||||
cache: "pnpm"
|
||||
|
||||
@@ -47,17 +47,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
node-version: "lts/krypton"
|
||||
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,3 +1,6 @@
|
||||
.DS_Store
|
||||
*.swp
|
||||
node_modules
|
||||
|
||||
docs/site
|
||||
docs/.cache
|
||||
Vendored
-2
@@ -3,8 +3,6 @@
|
||||
"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",
|
||||
|
||||
Vendored
+4
-4
@@ -3,9 +3,6 @@
|
||||
"editor.useTabStops": false,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"nuxtr.vueFiles.style.addStyleTag": false,
|
||||
"nuxtr.piniaFiles.defaultTemplate": "setup",
|
||||
"nuxtr.monorepoMode.DirectoryName": "src",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "always"
|
||||
},
|
||||
@@ -29,5 +26,8 @@
|
||||
"i18n-ally.sortKeys": false,
|
||||
"i18n-ally.keepFulfilled": false,
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"editor.gotoLocation.multipleDefinitions": "goto"
|
||||
"editor.gotoLocation.multipleDefinitions": "goto",
|
||||
"[toml]": {
|
||||
"editor.defaultFormatter": "tamasfe.even-better-toml"
|
||||
}
|
||||
}
|
||||
|
||||
+124
-2
@@ -7,14 +7,136 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Support disabling version check (https://github.com/wg-easy/wg-easy/pull/2648)
|
||||
- OAuth integration (https://github.com/wg-easy/wg-easy/pull/2659)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Button styling (https://github.com/wg-easy/wg-easy/pull/2666)
|
||||
- Escape Prometheus label values (https://github.com/wg-easy/wg-easy/pull/2702)
|
||||
- Skip ip6tables for firewall when IPv6 is disabled (https://github.com/wg-easy/wg-easy/pull/2701)
|
||||
|
||||
### Changed
|
||||
|
||||
- Only set sysctl if needed (https://github.com/wg-easy/wg-easy/pull/2630)
|
||||
- Improve security (https://github.com/wg-easy/wg-easy/pull/2661, https://github.com/wg-easy/wg-easy/pull/2668, https://github.com/wg-easy/wg-easy/pull/2669)
|
||||
- Replace mkdocs with zensical (https://github.com/wg-easy/wg-easy/pull/2667)
|
||||
- Migrate to Nuxt 4 (https://github.com/wg-easy/wg-easy/pull/2660)
|
||||
|
||||
## [15.3.0] - 2026-05-18
|
||||
|
||||
### Added
|
||||
|
||||
- AWG: support for H1-H4 ranges (https://github.com/wg-easy/wg-easy/pull/2480)
|
||||
- Client Firewall (https://github.com/wg-easy/wg-easy/pull/2418)
|
||||
- CLI: Show QR code (https://github.com/wg-easy/wg-easy/pull/2518)
|
||||
- Copy QR code to clipboard / save as png (https://github.com/wg-easy/wg-easy/pull/2521)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Add trailing newline to Prometheus metrics output (https://github.com/wg-easy/wg-easy/pull/2573)
|
||||
- Correctly use DEBUG env var (https://github.com/wg-easy/wg-easy/pull/2619)
|
||||
|
||||
### Changed
|
||||
|
||||
- Hooks are now Textareas (https://github.com/wg-easy/wg-easy/pull/2522)
|
||||
- Update to Node Krypton (24) (https://github.com/wg-easy/wg-easy/pull/2536)
|
||||
- Mobile UI (https://github.com/wg-easy/wg-easy/pull/2569)
|
||||
- Prevent enabling client when expired (https://github.com/wg-easy/wg-easy/pull/2594)
|
||||
|
||||
## [15.2.2] - 2026-02-06
|
||||
|
||||
### Added
|
||||
|
||||
- Added Userspace WireGuard support (https://github.com/wg-easy/wg-easy/pull/2419)
|
||||
|
||||
### Fixed
|
||||
|
||||
- LangSelector overlapping with Buttons (https://github.com/wg-easy/wg-easy/pull/2434)
|
||||
- AmnzeziaWG config parameters (https://github.com/wg-easy/wg-easy/pull/2440)
|
||||
- OpenMetrics help string format (https://github.com/wg-easy/wg-easy/pull/2453)
|
||||
- Reset 2fa when resetting admin password (https://github.com/wg-easy/wg-easy/pull/2461)
|
||||
|
||||
### Docs
|
||||
|
||||
- Replace Watchtower with maintained fork (https://github.com/wg-easy/wg-easy/pull/2456)
|
||||
|
||||
## [15.2.1] - 2026-01-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Icon in Searchbar (https://github.com/wg-easy/wg-easy/commit/458f66818a400f181e2c6326ede077c8793d71f2)
|
||||
- Interface save not working (https://github.com/wg-easy/wg-easy/commit/48f3fbd715a889e2425702a8a46332f2752aef91)
|
||||
- Error Messages in Setup (https://github.com/wg-easy/wg-easy/commit/32a055093a76342c40858d8dcf563b0700a8bd48)
|
||||
|
||||
## [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.
|
||||
|
||||
## Breaking Changes
|
||||
### Breaking Changes
|
||||
|
||||
As the whole setup has changed, we recommend to start from scratch. And import your existing configs.
|
||||
|
||||
## Major Changes
|
||||
### Major Changes
|
||||
|
||||
- Almost all Environment variables removed
|
||||
- New and Improved UI
|
||||
|
||||
+34
-8
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/library/node:lts-alpine AS build
|
||||
FROM docker.io/library/node:krypton-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
# update corepack
|
||||
@@ -7,16 +7,30 @@ RUN npm install --global corepack@latest
|
||||
RUN corepack enable pnpm
|
||||
|
||||
# Copy Web UI
|
||||
COPY src/package.json src/pnpm-lock.yaml ./
|
||||
COPY src/package.json src/pnpm-lock.yaml src/pnpm-workspace.yaml ./
|
||||
RUN pnpm install
|
||||
|
||||
# Build UI
|
||||
COPY src ./
|
||||
RUN pnpm build
|
||||
|
||||
# Build amneziawg-tools
|
||||
RUN apk add linux-headers build-base go git && \
|
||||
git clone https://github.com/amnezia-vpn/amneziawg-tools.git && \
|
||||
git clone https://github.com/amnezia-vpn/amneziawg-go && \
|
||||
cd amneziawg-go && \
|
||||
make && \
|
||||
cd ../amneziawg-tools/src && \
|
||||
make && \
|
||||
sed -i 's|\[\[ $proto == -4 \]\] && cmd sysctl -q net\.ipv4\.conf\.all\.src_valid_mark=1|[[ $proto == -4 ]] \&\& [[ $(sysctl -n net.ipv4.conf.all.src_valid_mark) != 1 ]] \&\& cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|' ./wg-quick/linux.bash
|
||||
|
||||
FROM docker.io/library/node:krypton-alpine AS build-libsql
|
||||
WORKDIR /app
|
||||
RUN npm install --no-save --omit=dev libsql
|
||||
|
||||
# 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:krypton-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"
|
||||
@@ -26,12 +40,18 @@ COPY --from=build /app/.output /app
|
||||
# Copy migrations
|
||||
COPY --from=build /app/server/database/migrations /app/server/database/migrations
|
||||
# libsql (https://github.com/nitrojs/nitro/issues/3328)
|
||||
RUN cd /app/server && \
|
||||
npm install --no-save libsql && \
|
||||
npm cache clean --force
|
||||
COPY --from=build-libsql /app/node_modules /app/server/node_modules
|
||||
|
||||
# cli
|
||||
COPY --from=build /app/cli/cli.sh /usr/local/bin/cli
|
||||
RUN chmod +x /usr/local/bin/cli
|
||||
# Copy amneziawg-go
|
||||
COPY --from=build /app/amneziawg-go/amneziawg-go /usr/bin/amneziawg-go
|
||||
RUN chmod +x /usr/bin/amneziawg-go
|
||||
# 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 \
|
||||
@@ -42,18 +62,24 @@ RUN apk add --no-cache \
|
||||
nftables \
|
||||
kmod \
|
||||
iptables-legacy \
|
||||
wireguard-tools
|
||||
wireguard-go \
|
||||
wireguard-tools && \
|
||||
sed -i 's|\[\[ $proto == -4 \]\] && cmd sysctl -q net\.ipv4\.conf\.all\.src_valid_mark=1|[[ $proto == -4 ]] \&\& [[ $(sysctl -n net.ipv4.conf.all.src_valid_mark) != 1 ]] \&\& cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|' /usr/bin/wg-quick
|
||||
|
||||
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
|
||||
|
||||
# Set Environment
|
||||
ENV DEBUG=Server,WireGuard,Database,CMD
|
||||
ENV DEBUG=Server,WireGuard,Database,CMD,Firewall
|
||||
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
-3
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/library/node:lts-alpine
|
||||
FROM docker.io/library/node:krypton-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# update corepack
|
||||
@@ -16,6 +16,7 @@ RUN apk add --no-cache \
|
||||
ip6tables \
|
||||
kmod \
|
||||
iptables-legacy \
|
||||
wireguard-go \
|
||||
wireguard-tools
|
||||
|
||||
# Use iptables-legacy
|
||||
@@ -23,14 +24,15 @@ RUN update-alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables
|
||||
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
|
||||
|
||||
# Set Environment
|
||||
ENV DEBUG=Server,WireGuard,Database,CMD
|
||||
ENV DEBUG=Server,WireGuard,Database,CMD,Firewall
|
||||
ENV PORT=51821
|
||||
ENV HOST=0.0.0.0
|
||||
ENV INSECURE=true
|
||||
ENV INIT_ENABLED=false
|
||||
ENV DISABLE_IPV6=false
|
||||
|
||||
# Install Dependencies
|
||||
COPY src/package.json src/pnpm-lock.yaml ./
|
||||
COPY src/package.json src/pnpm-lock.yaml src/pnpm-workspace.yaml ./
|
||||
RUN pnpm install
|
||||
|
||||
# Copy Project
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
# WireGuard Easy
|
||||
|
||||
[](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml)
|
||||
[](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml)
|
||||
[](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml)
|
||||
[](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)
|
||||
[](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
|
||||
@@ -33,6 +31,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
|
||||
- IPv6 support
|
||||
- CIDR support
|
||||
- 2FA support
|
||||
- Per-client firewall filtering (requires iptables)
|
||||
|
||||
> [!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)
|
||||
@@ -68,11 +67,11 @@ And log in again.
|
||||
|
||||
The easiest way to run WireGuard Easy is with Docker Compose.
|
||||
|
||||
Just download [`docker-compose.yml`](docker-compose.yml) and execute `sudo docker compose up -d`.
|
||||
Just follow [these steps](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/) in the detailed documentation.
|
||||
|
||||
Now setup a reverse proxy to be able to access the Web UI securely from the internet.
|
||||
You can also install WireGuard Easy with the [docker run command](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/docker-run/) or via [podman](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/podman-nft/).
|
||||
|
||||
If you want to access the Web UI over HTTP, change the env var `INSECURE` to `true`. This is not recommended. Only use this for testing
|
||||
Now [setup a reverse proxy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/#setup-reverse-proxy) to be able to access the Web UI securely from the internet. This step is optional, just make sure to follow the guide [here](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/reverse-proxyless/) if you decide not to do it.
|
||||
|
||||
## Donate
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"fill" : {
|
||||
"automatic-gradient" : "display-p3:0.48853,0.13220,0.12335,1.00000"
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"fill" : {
|
||||
"automatic-gradient" : "srgb:1.00000,1.00000,1.00000,1.00000"
|
||||
},
|
||||
"image-name" : "wireguard-logo.png",
|
||||
"name" : "wireguard-logo",
|
||||
"position" : {
|
||||
"scale" : 0.5,
|
||||
"translation-in-points" : [
|
||||
255.828125,
|
||||
-225.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fill-specializations" : [
|
||||
{
|
||||
"value" : {
|
||||
"automatic-gradient" : "srgb:1.00000,1.00000,1.00000,1.00000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"appearance" : "dark",
|
||||
"value" : {
|
||||
"automatic-gradient" : "display-p3:0.48853,0.13220,0.12335,1.00000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"image-name" : "ticket.png",
|
||||
"name" : "ticket",
|
||||
"position" : {
|
||||
"scale" : 1.2,
|
||||
"translation-in-points" : [
|
||||
-119.91562499999998,
|
||||
165.65625
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 167 KiB |
+19
-10
@@ -2,17 +2,15 @@
|
||||
|
||||
First and foremost, thank you! We appreciate that you want to contribute to wg-easy, your time is valuable, and your contributions mean a lot to us.
|
||||
|
||||
|
||||
## Important!
|
||||
|
||||
By contributing to this project, you:
|
||||
|
||||
* Agree that you have authored 100% of the content
|
||||
* Agree that you have the necessary rights to the content
|
||||
* Agree that you have received the necessary permissions from your employer to make the contributions (if applicable)
|
||||
* Agree that the content you contribute may be provided under the Project license(s)
|
||||
* Agree that, if you did not author 100% of the content, the appropriate licenses and copyrights have been added along with any other necessary attribution.
|
||||
|
||||
- Agree that you have authored 100% of the content
|
||||
- Agree that you have the necessary rights to the content
|
||||
- Agree that you have received the necessary permissions from your employer to make the contributions (if applicable)
|
||||
- Agree that the content you contribute may be provided under the Project license(s)
|
||||
- Agree that, if you did not author 100% of the content, the appropriate licenses and copyrights have been added along with any other necessary attribution.
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -24,7 +22,6 @@ Creating an issue is the simplest form of contributing to a project. But there a
|
||||
- Feature requests
|
||||
- Bug reports
|
||||
|
||||
|
||||
## Showing support for wg-easy
|
||||
|
||||
Please keep in mind that open source software is built by people like you, who spend their free time creating things the rest the community can use.
|
||||
@@ -34,6 +31,20 @@ Don't have time to contribute? No worries, here are some other ways to show your
|
||||
- star the [project](https://github.com/wg-easy/wg-easy)
|
||||
- tweet your support for wg-easy
|
||||
|
||||
## AI Contribution Policy
|
||||
|
||||
Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly and disclose that use clearly.
|
||||
|
||||
1. Review AI-generated code closely before marking a pull request ready for review.
|
||||
2. Manually test the changes and add appropriate automated tests where feasible.
|
||||
3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review.
|
||||
4. Disclose AI-assisted content clearly.
|
||||
5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
|
||||
6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit.
|
||||
|
||||
Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves.
|
||||
|
||||
We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process.
|
||||
|
||||
## Issues
|
||||
|
||||
@@ -71,7 +82,6 @@ Please be as descriptive as possible when creating an issue. Give us the informa
|
||||
- **error messages**: (required) please paste any error messages into the issue, or a [gist](https://gist.github.com/)
|
||||
- **extensions, plugins, helpers, etc** (if applicable): please list any extensions you're using
|
||||
|
||||
|
||||
### Closing issues
|
||||
|
||||
The original poster or the maintainers of wg-easy may close an issue at any time. Typically, but not exclusively, issues are closed when:
|
||||
@@ -81,7 +91,6 @@ The original poster or the maintainers of wg-easy may close an issue at any time
|
||||
- An issue is clearly a duplicate of another issue, in which case the duplicate issue will be linked.
|
||||
- A discussion has clearly run its course
|
||||
|
||||
|
||||
## Next steps
|
||||
|
||||
**Tips for creating idiomatic issues**
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
volumes:
|
||||
etc_wireguard:
|
||||
|
||||
services:
|
||||
wg-easy:
|
||||
#environment:
|
||||
# Optional:
|
||||
# - PORT=51821
|
||||
# - HOST=0.0.0.0
|
||||
# - INSECURE=false
|
||||
|
||||
image: ghcr.io/wg-easy/wg-easy:15
|
||||
container_name: wg-easy
|
||||
networks:
|
||||
wg:
|
||||
ipv4_address: 10.42.42.42
|
||||
ipv6_address: fdcc:ad94:bacf:61a3::2a
|
||||
volumes:
|
||||
- etc_wireguard:/etc/wireguard
|
||||
- /lib/modules:/lib/modules:ro
|
||||
ports:
|
||||
- "51820:51820/udp"
|
||||
- "51821:51821/tcp"
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
# - NET_RAW # ⚠️ Uncomment if using Podman Compose
|
||||
sysctls:
|
||||
- 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
|
||||
|
||||
networks:
|
||||
wg:
|
||||
driver: bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 10.42.42.0/24
|
||||
- subnet: fdcc:ad94:bacf:61a3::/64
|
||||
+24
-25
@@ -3,35 +3,21 @@ volumes:
|
||||
|
||||
services:
|
||||
wg-easy:
|
||||
environment:
|
||||
# Change Language:
|
||||
# (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi)
|
||||
- LANG=de
|
||||
# ⚠️ Required:
|
||||
# Change this to your host's public address
|
||||
- WG_HOST=raspberrypi.local
|
||||
#environment:
|
||||
# Optional:
|
||||
# - PORT=51821
|
||||
# - HOST=0.0.0.0
|
||||
# - INSECURE=false
|
||||
|
||||
# Optional:
|
||||
# - PASSWORD_HASH=$$2y$$10$$hBCoykrB95WSzuV4fafBzOHWKu9sbyVa34GJr8VV5R/pIelfEMYyG # (needs double $$, hash of 'foobar123'; see "How_to_generate_an_bcrypt_hash.md" for generate the hash)
|
||||
# - PORT=51821
|
||||
# - WG_PORT=51820
|
||||
# - WG_CONFIG_PORT=92820
|
||||
# - WG_DEFAULT_ADDRESS=10.8.0.x
|
||||
# - WG_DEFAULT_DNS=1.1.1.1
|
||||
# - WG_MTU=1420
|
||||
# - WG_ALLOWED_IPS=192.168.15.0/24, 10.0.1.0/24
|
||||
# - WG_PERSISTENT_KEEPALIVE=25
|
||||
# - WG_PRE_UP=echo "Pre Up" > /etc/wireguard/pre-up.txt
|
||||
# - WG_POST_UP=echo "Post Up" > /etc/wireguard/post-up.txt
|
||||
# - WG_PRE_DOWN=echo "Pre Down" > /etc/wireguard/pre-down.txt
|
||||
# - WG_POST_DOWN=echo "Post Down" > /etc/wireguard/post-down.txt
|
||||
# - UI_TRAFFIC_STATS=true
|
||||
# - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart)
|
||||
|
||||
image: ghcr.io/wg-easy/wg-easy:14
|
||||
image: ghcr.io/wg-easy/wg-easy:15
|
||||
container_name: wg-easy
|
||||
networks:
|
||||
wg:
|
||||
ipv4_address: 10.42.42.42
|
||||
ipv6_address: fdcc:ad94:bacf:61a3::2a
|
||||
volumes:
|
||||
- etc_wireguard:/etc/wireguard
|
||||
- /lib/modules:/lib/modules:ro
|
||||
ports:
|
||||
- "51820:51820/udp"
|
||||
- "51821:51821/tcp"
|
||||
@@ -43,3 +29,16 @@ services:
|
||||
sysctls:
|
||||
- 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
|
||||
|
||||
networks:
|
||||
wg:
|
||||
driver: bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 10.42.42.0/24
|
||||
- subnet: fdcc:ad94:bacf:61a3::/64
|
||||
|
||||
@@ -5,6 +5,7 @@ title: API
|
||||
/// warning | Breaking Changes
|
||||
|
||||
This API is not yet stable and may change in the future. The API is currently in development and is subject to change without notice. The API is not yet documented, but we will add documentation as the API stabilizes.
|
||||
|
||||
///
|
||||
|
||||
You can use the API to interact with the application programmatically. The API is available at `/api` and supports both GET and POST requests. The API is designed to be simple and easy to use, with a focus on providing a consistent interface for all endpoints.
|
||||
|
||||
@@ -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 | :white_check_mark: Yes | :white_check_mark: | :white_check_mark: |
|
||||
| Jmin | :white_check_mark: Yes | :white_check_mark: | :white_check_mark: |
|
||||
| Jmax | :white_check_mark: Yes | :white_check_mark: | :white_check_mark: |
|
||||
| S1-S4 | :x: No, must match | :white_check_mark: | :x: (copied from server) |
|
||||
| H1-H4 | :x: No, must match | :white_check_mark: | :x: (copied from server) |
|
||||
| I1-I5 | :white_check_mark: Yes | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
## Client Applications
|
||||
|
||||
To be able to connect to wg-easy if AmneziaWG is enabled, you must have an AmneziaWG-compatible client. Where an AmneziaWG app is available for your platform, it is recommended to use it rather than Amnezia VPN.
|
||||
|
||||
Android:
|
||||
|
||||
- [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
|
||||
- [Amnezia VPN](https://play.google.com/store/apps/details?id=org.amnezia.vpn) - Amnezia VPN Official Client
|
||||
|
||||
iOS and macOS:
|
||||
|
||||
- [AmneziaWG](https://apps.apple.com/us/app/amneziawg/id6478942365) - AmneziaWG Official Client
|
||||
- [Amnezia VPN](https://apps.apple.com/us/app/amneziavpn/id1600529900) - Amnezia VPN Official Client
|
||||
|
||||
Windows:
|
||||
|
||||
- [AmneziaWG](https://github.com/amnezia-vpn/amneziawg-windows-client/releases) - AmneziaWG Official Client (Requires building from source code)
|
||||
- [Amnezia VPN](https://amnezia.org/downloads) - Amnezia VPN 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) |
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
title: External Authentication
|
||||
---
|
||||
|
||||
## OAuth
|
||||
|
||||
### Setup
|
||||
|
||||
To enable OAuth set the env var `OAUTH_PROVIDERS` to any of the following providers:
|
||||
|
||||
| Provider | Value |
|
||||
| ----------------------------- | -------- |
|
||||
| [Google](#google) | `google` |
|
||||
| [GitHub](#github) | `github` |
|
||||
| [Generic OIDC](#generic-oidc) | `oidc` |
|
||||
|
||||
You can enable multiple providers by separating them with a comma:
|
||||
|
||||
e.g. `google,github`
|
||||
|
||||
### Auto Register
|
||||
|
||||
To automatically register users that log in with an OAuth provider, set the following environment variable to `true`:
|
||||
|
||||
| Env | Required | Default | Description |
|
||||
| --------------------- | -------- | ------- | ------------------------ |
|
||||
| `OAUTH_AUTO_REGISTER` | :x: | `false` | Enable auto-registration |
|
||||
|
||||
When enabled:
|
||||
|
||||
- If a user logs in with an email address that is not yet registered, a new account will be created for them.
|
||||
|
||||
- If a user logs in with an email address that is already registered, their account will be linked to the OAuth provider (if not already linked), regardless of the value of `OAUTH_AUTO_REGISTER`.
|
||||
|
||||
/// warning | Security
|
||||
|
||||
Users will be created with Admin Permissions, as the permissions system is not yet implemented. Only enable this if you trust all users that can log in with the OAuth provider.
|
||||
|
||||
Use [Allowed Domains](#allowed-domains) to restrict which users can log in.
|
||||
|
||||
///
|
||||
|
||||
### Allowed Domains
|
||||
|
||||
To only allow users with an email address from a specific domain to log in, set the following environment variable to the allowed domain.
|
||||
|
||||
| Env | Required | Default | Description |
|
||||
| ----------------------- | -------- | ------- | --------------------- |
|
||||
| `OAUTH_ALLOWED_DOMAINS` | :x: | - | Allowed email domains |
|
||||
|
||||
You can allow multiple domains by separating them with a comma:
|
||||
|
||||
e.g. `example.com,example.org`
|
||||
|
||||
### Auto Launch
|
||||
|
||||
To automatically launch the OAuth login flow when visiting the login page, set the following environment variable to the provider you want to launch:
|
||||
|
||||
| Env | Required | Default | Description |
|
||||
| ------------------- | -------- | ------- | ----------------------------- |
|
||||
| `OAUTH_AUTO_LAUNCH` | :x: | - | Auto launch an OAuth provider |
|
||||
|
||||
When enabled:
|
||||
|
||||
- Visiting the login page will automatically redirect to the selected provider's login page
|
||||
- The user can still access the normal login page by visiting `/login?auto_launch=false`
|
||||
- You can auto launch any provider by visiting `/login?auto_launch=<provider>`
|
||||
|
||||
### Redirect URIs
|
||||
|
||||
You have to configure the following redirect URIs in your OAuth provider:
|
||||
|
||||
- `https://<your-domain>/api/auth/<provider>/callback`
|
||||
Used to log in to with the provider
|
||||
- `https://<your-domain>/api/auth/<provider>/link`
|
||||
Used to link an existing account to the provider
|
||||
|
||||
If your provider does not support multiple redirect URIs (e.g. GitHub) but allows multiple URIs under the same base, then configure:
|
||||
|
||||
- `https://<your-domain>/api/auth/<provider>/`
|
||||
|
||||
### Provider Configuration
|
||||
|
||||
#### Google
|
||||
|
||||
| Env | Required | Description |
|
||||
| ---------------------------- | ------------------ | -------------------- |
|
||||
| `OAUTH_GOOGLE_CLIENT_ID` | :white_check_mark: | Google Client ID |
|
||||
| `OAUTH_GOOGLE_CLIENT_SECRET` | :white_check_mark: | Google Client Secret |
|
||||
|
||||
<h5>Setup</h5>
|
||||
|
||||
1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
|
||||
2. Create an OAuth 2.0 Client ID (Web application)
|
||||
3. Add Authorized redirect URI: See [Redirect URIs](#redirect-uris)
|
||||
4. Copy the Client ID and Client Secret to the environment variables
|
||||
|
||||
#### GitHub
|
||||
|
||||
| Env | Required | Description |
|
||||
| ---------------------------- | ------------------ | -------------------- |
|
||||
| `OAUTH_GITHUB_CLIENT_ID` | :white_check_mark: | GitHub Client ID |
|
||||
| `OAUTH_GITHUB_CLIENT_SECRET` | :white_check_mark: | GitHub Client Secret |
|
||||
|
||||
<h5>Setup</h5>
|
||||
|
||||
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
|
||||
2. Create a new OAuth App
|
||||
3. Add Authorization callback URL: See [Redirect URIs](#redirect-uris)
|
||||
4. Create a new client secret
|
||||
5. Copy the Client ID and Client Secret to the environment variables
|
||||
|
||||
#### Generic OIDC
|
||||
|
||||
This supports generic OIDC providers like Authelia, Authentik, etc.
|
||||
|
||||
The provider needs to support:
|
||||
|
||||
- PKCE
|
||||
- default scopes: `openid email profile`
|
||||
- Client Secret Authentication `client_secret_post`
|
||||
|
||||
The provider needs to be available with HTTPS and have a valid certificate.
|
||||
|
||||
| Env | Required | Default | Example | Description |
|
||||
| -------------------------- | ------------------ | ------- | -------------------------- | ------------------ |
|
||||
| `OAUTH_OIDC_SERVER` | :white_check_mark: | - | `https://auth.example.com` | OIDC Server |
|
||||
| `OAUTH_OIDC_CLIENT_ID` | :white_check_mark: | - | - | OIDC Client ID |
|
||||
| `OAUTH_OIDC_CLIENT_SECRET` | :white_check_mark: | - | - | OIDC Client Secret |
|
||||
| `OAUTH_OIDC_NAME` | :x: | OIDC | `Authelia` | Provider Name |
|
||||
|
||||
##### Authelia Setup
|
||||
|
||||
Generate Client ID and Secret:
|
||||
|
||||
```shell
|
||||
# Client ID
|
||||
docker run --rm authelia/authelia:latest authelia crypto rand --length 72 --charset rfc3986
|
||||
# Client Secret
|
||||
docker run --rm authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986
|
||||
```
|
||||
|
||||
```yaml
|
||||
- client_id: '...'
|
||||
client_name: wg-easy
|
||||
client_secret: '$pbkdf2-...'
|
||||
redirect_uris:
|
||||
- https://<your-domain>/api/auth/oidc/callback
|
||||
- https://<your-domain>/api/auth/oidc/link
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
authorization_policy: one_factor
|
||||
pre_configured_consent_duration: 1 week
|
||||
require_pkce: true
|
||||
token_endpoint_auth_method: client_secret_post
|
||||
```
|
||||
|
||||
#### Generic OAuth
|
||||
|
||||
Not currently supported
|
||||
|
||||
### Disable Password Authentication
|
||||
|
||||
To disable password-based authentication and only allow login via OAuth providers, set the following environment variable to `true`:
|
||||
|
||||
| Env | Required | Default | Description |
|
||||
| ----------------------- | -------- | ------- | ------------------------------- |
|
||||
| `DISABLE_PASSWORD_AUTH` | :x: | `false` | Disable password authentication |
|
||||
|
||||
When enabled:
|
||||
|
||||
- Users will not be able to log in with a password
|
||||
|
||||
/// warning | Access Recovery
|
||||
|
||||
Before disabling password authentication, ensure that at least one OAuth provider is configured and that you have successfully linked an administrator account.
|
||||
|
||||
If no login method is available, you will not be able to log in to the application and will need to reset the configuration to regain access.
|
||||
|
||||
///
|
||||
@@ -4,8 +4,20 @@ title: Optional Configuration
|
||||
|
||||
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 |
|
||||
| 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 |
|
||||
| `DISABLE_VERSION_CHECK` | `false` | `true` | If wg-easy should check for new updates |
|
||||
|
||||
/// 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.
|
||||
|
||||
///
|
||||
|
||||
@@ -6,22 +6,24 @@ If you want to run the setup without any user interaction, e.g. with a tool like
|
||||
|
||||
These will only be used during the first start of the container. After that, the setup will be disabled.
|
||||
|
||||
| Env | Example | Description | Group |
|
||||
| ---------------- | ----------------- | --------------------------------------------------------- | ----- |
|
||||
| `INIT_ENABLED` | `true` | Enables the below env vars | 0 |
|
||||
| `INIT_USERNAME` | `admin` | Sets admin username | 1 |
|
||||
| `INIT_PASSWORD` | `Se!ureP%ssw` | Sets admin password | 1 |
|
||||
| `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 1 |
|
||||
| `INIT_PORT` | `51820` | Port clients will connect to and wireguard will listen on | 1 |
|
||||
| `INIT_DNS` | `1.1.1.1,8.8.8.8` | Sets global dns setting | 2 |
|
||||
| `INIT_IPV4_CIDR` | `10.8.0.0/24` | Sets IPv4 cidr | 3 |
|
||||
| `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 3 |
|
||||
| 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
|
||||
/// 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
|
||||
@@ -29,4 +31,5 @@ If you want to skip the setup process, you have to configure group `1`
|
||||
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.
|
||||
|
||||
///
|
||||
|
||||
@@ -39,4 +39,5 @@ You can use the following Grafana dashboard to visualize the metrics:
|
||||
|
||||
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, 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.
|
||||
- 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,10 +34,10 @@ 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
|
||||
@@ -47,6 +49,10 @@ In the setup wizard, select that you already have a configuration file and uploa
|
||||
[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`.
|
||||
|
||||
@@ -9,6 +9,7 @@ This project is Open Source. That means that you can contribute on enhancements,
|
||||
/// note | Attention
|
||||
|
||||
**Before opening an issue**, read the [`README`][github-file-readme] carefully, study the docs for your version (maybe [latest][docs-latest]) and your search engine you trust. The issue tracker is not meant to be used for unrelated questions!
|
||||
|
||||
///
|
||||
|
||||
When opening an issue, please provide details use case to let the community reproduce your problem.
|
||||
@@ -16,6 +17,7 @@ When opening an issue, please provide details use case to let the community repr
|
||||
/// note | Attention
|
||||
|
||||
**Use the issue templates** to provide the necessary information. Issues which do not use these templates are not worked on and closed.
|
||||
|
||||
///
|
||||
|
||||
By raising issues, I agree to these terms and I understand, that the rules set for the issue tracker will help both maintainers as well as everyone to find a solution.
|
||||
@@ -39,6 +41,7 @@ When an option is marked with "not officially supported" / "unsupported", then s
|
||||
/// question | Motivation
|
||||
|
||||
You want to add a feature? Feel free to start creating an issue explaining what you want to do and how you're thinking doing it. Other users may have the same need and collaboration may lead to better results.
|
||||
|
||||
///
|
||||
|
||||
### Submit a Pull-Request
|
||||
|
||||
@@ -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.
|
||||
@@ -2,8 +2,180 @@
|
||||
title: AdGuard Home
|
||||
---
|
||||
|
||||
It seems like the Docs on how to setup AdGuard Home are not available yet.
|
||||
This tutorial is a follow-up to the official [Traefik tutorial](./traefik.md). It will guide you through integrating AdGuard Home with your existing `wg-easy` and Traefik setup to provide network-wide DNS ad-blocking.
|
||||
|
||||
Feel free to create a PR and add them here.
|
||||
## Prerequisites
|
||||
|
||||
<!-- TODO -->
|
||||
- A working [wg-easy](./basic-installation.md) and [Traefik](./traefik.md) setup from the previous guides.
|
||||
|
||||
/// warning | Important: Following this guide will reset your WireGuard configuration.
|
||||
|
||||
The process involves re-creating the `wg-easy` container and its data, which means **all existing WireGuard clients and settings will be deleted.**
|
||||
|
||||
You will need to create your clients again after completing this guide.
|
||||
|
||||
///
|
||||
|
||||
## Add `adguard` configuration
|
||||
|
||||
1. Create a directory for the configuration files:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /etc/docker/containers/adguard
|
||||
```
|
||||
|
||||
2. Create volumes for persistent data:
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /etc/docker/volumes/adguard/adguard_work
|
||||
sudo mkdir -p /etc/docker/volumes/adguard/adguard_conf
|
||||
sudo chmod -R 700 /etc/docker/volumes/adguard
|
||||
```
|
||||
|
||||
3. Create the `docker-compose.yml` file.
|
||||
|
||||
File: `/etc/docker/containers/adguard/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
adguard:
|
||||
image: adguard/adguardhome:v0.107.64
|
||||
container_name: adguard
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /etc/docker/volumes/adguard/adguard_work:/opt/adguardhome/work
|
||||
- /etc/docker/volumes/adguard/adguard_conf:/opt/adguardhome/conf
|
||||
networks:
|
||||
wg:
|
||||
interface_name: eth0
|
||||
ipv4_address: 10.42.42.43
|
||||
ipv6_address: fdcc:ad94:bacf:61a3::2b
|
||||
traefik:
|
||||
interface_name: eth1
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.adguard.rule=Host(`adguard.$example.com$`)'
|
||||
- 'traefik.http.routers.adguard.entrypoints=websecure'
|
||||
- 'traefik.http.routers.adguard.service=adguard'
|
||||
- 'traefik.http.services.adguard.loadbalancer.server.port=3000'
|
||||
- 'traefik.docker.network=traefik'
|
||||
|
||||
networks:
|
||||
wg:
|
||||
external: true
|
||||
traefik:
|
||||
external: true
|
||||
```
|
||||
|
||||
## Update `wg-easy` configuration
|
||||
|
||||
Modify the corresponding sections of your existing `wg-easy` compose file to match the updated version below.
|
||||
|
||||
File: `/etc/docker/containers/wg-easy/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wg-easy:
|
||||
ports:
|
||||
- "51820:51820/udp"
|
||||
...
|
||||
networks:
|
||||
wg:
|
||||
interface_name: eth0
|
||||
...
|
||||
traefik:
|
||||
interface_name: eth1
|
||||
...
|
||||
...
|
||||
environment:
|
||||
# Unattended Setup
|
||||
- INIT_ENABLED=true
|
||||
# Replace $username$ with your username
|
||||
- INIT_USERNAME=$username$
|
||||
# Replace $password$ with your unhashed password
|
||||
- INIT_PASSWORD=$password$
|
||||
# Replace $example.com$ with your domain
|
||||
- INIT_HOST=wg-easy.$example.com$
|
||||
- INIT_PORT=51820
|
||||
- INIT_DNS=10.42.42.43,fdcc:ad94:bacf:61a3::2b
|
||||
- INIT_IPV4_CIDR=10.8.0.0/24
|
||||
- INIT_IPV6_CIDR=fd42:42:42::/64
|
||||
...
|
||||
|
||||
networks:
|
||||
wg:
|
||||
# Prevents Docker Compose from prefixing the network name.
|
||||
name: wg
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
## Setup Wireguard
|
||||
|
||||
1. Restart `wg-easy`:
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker compose down -v
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
2. Edit Wireguard's Hooks.
|
||||
|
||||
In the Admin Panel of your WireGuard server, go to the Hooks tab and replace it with:
|
||||
|
||||
**_PostUp_**
|
||||
|
||||
```shell
|
||||
iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -t nat -A PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination 10.42.42.43; iptables -t nat -A PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination 10.42.42.43; ip6tables -t nat -A PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b; ip6tables -t nat -A PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; ip6tables -t nat -A POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE;
|
||||
```
|
||||
|
||||
**_PostDown_**
|
||||
|
||||
```shell
|
||||
iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT || true; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT || true; iptables -t nat -D PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination 10.42.42.43 || true; iptables -t nat -D PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination 10.42.42.43 || true; ip6tables -t nat -D PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b || true; ip6tables -t nat -D PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::2b || true; iptables -D FORWARD -i wg0 -j ACCEPT || true; iptables -D FORWARD -o wg0 -j ACCEPT || true; ip6tables -D FORWARD -i wg0 -j ACCEPT || true; ip6tables -D FORWARD -o wg0 -j ACCEPT || true; iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE || true; ip6tables -t nat -D POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE || true;
|
||||
```
|
||||
|
||||
3. Restart `wg-easy` to apply changes:
|
||||
|
||||
```shell
|
||||
sudo docker restart wg-easy
|
||||
```
|
||||
|
||||
## Setup Adguard Home
|
||||
|
||||
1. Start `adguard` service:
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/adguard
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
2. Navigate to `https://adguard.$example.com$` to begin the AdGuard Home setup.
|
||||
|
||||
/// warning | Important: Configure AdGuard Home Admin Web Interface Port
|
||||
|
||||
During the initial AdGuard Home setup on the `Step 2/5` page, you **must** set the **Admin Web Interface Port** to **3000**. Do not use the default port 80, as it will not work with the Traefik configuration.
|
||||
|
||||
After completing the setup, the AdGuard UI might appear unresponsive. This is expected. **Simply reload the page**, and the panel will display correctly.
|
||||
|
||||
> If you accidentally left it default (80), you will need to manually edit the `docker-compose.yml` file for AdGuard Home (`/etc/docker/containers/adguard/docker-compose.yml`) and change the line `traefik.http.services.adguard.loadbalancer.server.port=3000` to `traefik.http.services.adguard.loadbalancer.server.port=80`. After making this change, restart AdGuard Home by navigating to `/etc/docker/containers/adguard` and running `sudo docker compose up -d`.
|
||||
|
||||
///
|
||||
|
||||
## Final System Checks
|
||||
|
||||
### Firewall
|
||||
|
||||
Ensure the ports `80/tcp`, `443/tcp`, `443/udp`, and `51820/udp` are open.
|
||||
|
||||
### Optional: Optimizing UDP Buffer Sizes
|
||||
|
||||
AdGuard Home, as a DNS server, handles a large volume of UDP packets. To ensure optimal performance, it is recommended to increase the system's UDP buffer sizes. You can apply these settings using your system's `sysctl` configuration (e.g., by creating a file in `/etc/sysctl.d/`).
|
||||
|
||||
```shell
|
||||
net.core.rmem_max = 7500000
|
||||
net.core.wmem_max = 7500000
|
||||
```
|
||||
|
||||
After adding these settings, remember to apply them (e.g., by running `sudo sysctl --system` or rebooting)
|
||||
|
||||
@@ -20,7 +20,7 @@ File: `/etc/docker/containers/watchtower/docker-compose.yml`
|
||||
```yaml
|
||||
services:
|
||||
watchtower:
|
||||
image: containrrr/watchtower:latest
|
||||
image: nickfedor/watchtower:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
env_file:
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
title: Basic Installation
|
||||
---
|
||||
|
||||
<!-- TOOD: add docs for pihole, nginx, caddy, traefik -->
|
||||
<!-- TODO: add docs for pihole, nginx, caddy, traefik -->
|
||||
|
||||
## Requirements
|
||||
|
||||
1. You need to have a host that you can manage
|
||||
2. You need to have a domain name or a public IP address
|
||||
3. You need a supported architecture (x86_64, arm64, armv7)
|
||||
3. You need a supported architecture (x86_64, arm64)
|
||||
4. You need curl installed on your host
|
||||
|
||||
## Install Docker
|
||||
@@ -33,7 +33,7 @@ Follow the Docs here: <https://docs.docker.com/engine/install/> and install Dock
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker-compose up -d
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
## Setup Firewall
|
||||
@@ -56,8 +56,8 @@ To update `wg-easy` to the latest version, run:
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker-compose pull
|
||||
sudo docker-compose up -d
|
||||
sudo docker compose pull
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
## Auto Update
|
||||
|
||||
@@ -2,8 +2,102 @@
|
||||
title: Caddy
|
||||
---
|
||||
|
||||
It seems like the Docs on how to setup Caddy are not available yet.
|
||||
/// note | Opinionated
|
||||
|
||||
Feel free to create a PR and add them here.
|
||||
This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths.
|
||||
|
||||
<!-- TODO -->
|
||||
///
|
||||
|
||||
We're using [Caddy](https://caddyserver.com/) here as 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.
|
||||
|
||||
@@ -7,9 +7,9 @@ To setup the IPv6 Network, simply run once:
|
||||
```shell
|
||||
docker network create \
|
||||
-d bridge --ipv6 \
|
||||
-d default \
|
||||
--subnet 10.42.42.0/24 \
|
||||
--subnet fdcc:ad94:bacf:61a3::/64 wg \
|
||||
--subnet fdcc:ad94:bacf:61a3::/64 \
|
||||
wg
|
||||
```
|
||||
|
||||
<!-- ref: major version -->
|
||||
|
||||
@@ -7,6 +7,7 @@ title: No Reverse Proxy
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
|
||||
///
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
@@ -141,10 +142,10 @@ sudo docker network create traefik
|
||||
## Start traefik
|
||||
|
||||
```shell
|
||||
sudo docker-compose up -d
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
You can no access the Traefik dashboard at `https://traefik.$example.com$` with the credentials you set in `traefik_dynamic.yml`.
|
||||
You can now access the Traefik dashboard at `https://traefik.$example.com$` with the credentials you set in `traefik_dynamic.yml`.
|
||||
|
||||
## Add Labels to `wg-easy`
|
||||
|
||||
@@ -166,6 +167,7 @@ services:
|
||||
- "traefik.http.routers.wg-easy.entrypoints=websecure"
|
||||
- "traefik.http.routers.wg-easy.service=wg-easy"
|
||||
- "traefik.http.services.wg-easy.loadbalancer.server.port=51821"
|
||||
- "traefik.docker.network=traefik"
|
||||
...
|
||||
|
||||
networks:
|
||||
@@ -178,7 +180,7 @@ networks:
|
||||
|
||||
```shell
|
||||
cd /etc/docker/containers/wg-easy
|
||||
sudo docker-compose up -d
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
You can now access `wg-easy` at `https://wg-easy.$example.com$` and start the setup.
|
||||
|
||||
@@ -6,6 +6,20 @@ hide:
|
||||
|
||||
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.
|
||||
|
||||
## How do I restrict client access to specific networks or servers?
|
||||
|
||||
Use the **Per-Client Firewall** feature to enforce server-side restrictions on what each client can access.
|
||||
|
||||
**Requirements:** This feature requires `iptables` (and `ip6tables` for IPv6) to be installed on the host system.
|
||||
|
||||
1. Enable "Per-Client Firewall" in **Admin Panel → Interface**
|
||||
2. Edit a client and configure "Firewall Allowed IPs"
|
||||
3. Specify which destinations the client should be allowed to access
|
||||
|
||||
Unlike "Allowed IPs" which only controls client-side routing, firewall rules are enforced by the server and cannot be bypassed.
|
||||
|
||||
See the [Admin Panel Guide](./guides/admin.md#per-client-firewall) and [Client Guide](./guides/clients.md#firewall-allowed-ips) for detailed configuration.
|
||||
|
||||
## 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.
|
||||
@@ -95,3 +109,31 @@ To resolve this issue, you can try the following steps:
|
||||
```shell
|
||||
echo "ip6table_filter" | sudo tee -a /etc/modules
|
||||
```
|
||||
|
||||
## Clients lose connectivity after restarting the container when using multiple networks?
|
||||
|
||||
When you attach multiple Docker networks (e.g., `wg` and a reverse proxy network like `traefik` or `nginx`) to the `wg-easy` container, Docker might assign the network interfaces randomly (e.g., swapping `eth0` and `eth1`). Since `wg-easy` expects the wireguard interface to act as `eth0` and configures `POSTROUTING` rules for it, connectivity will break if the interfaces are swapped upon container restart.
|
||||
|
||||
To solve this, specify the `interface_name` and `gw_priority` explicitly in your `docker-compose.yml` file to guarantee that the `wg` network always binds to `eth0` and acts as the default gateway.
|
||||
|
||||
**Example `docker-compose.yml`:**
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wg-easy:
|
||||
# ... other configuration ...
|
||||
networks:
|
||||
wg:
|
||||
interface_name: eth0
|
||||
gw_priority: 1
|
||||
ipv4_address: 10.42.42.42
|
||||
nginx:
|
||||
interface_name: eth1
|
||||
gw_priority: 0
|
||||
|
||||
networks:
|
||||
wg:
|
||||
# ... wg network config ...
|
||||
nginx:
|
||||
external: true
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ Before you can get started with deploying your own VPN, there are some requireme
|
||||
|
||||
1. You need to have a host that you can manage
|
||||
2. You need to have a domain name or a public IP address
|
||||
3. You need a supported architecture (x86_64, arm64, armv7)
|
||||
3. You need a supported architecture (x86_64, arm64)
|
||||
|
||||
### Host Setup
|
||||
|
||||
@@ -24,6 +24,7 @@ There are a few requirements for a suitable host system:
|
||||
|
||||
On the host, you need to have a suitable container runtime (like _Docker_ or _Podman_) installed. We assume [_Docker Compose_][docker-compose] is [installed][docker-compose-installation]. We have aligned file names and configuration conventions with the latest [Docker Compose specification][docker-compose-specification].
|
||||
If you're using podman, make sure to read the related [documentation][docs-podman].
|
||||
|
||||
///
|
||||
|
||||
[docker-compose]: https://docs.docker.com/compose/
|
||||
@@ -38,24 +39,26 @@ 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.
|
||||
|
||||
| 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 |
|
||||
| `latest` | latest tag | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | points to the v14 release, should be avoided |
|
||||
|
||||
<!-- ref: major version -->
|
||||
<!-- ref: major version (check links too) -->
|
||||
|
||||
When publishing a tag we follow the [Semantic Versioning][semver] specification. The `latest` tag is always pointing to the latest stable release. If you want to avoid breaking changes, use the major version tag (e.g. `15`).
|
||||
When publishing a tag we follow the [Semantic Versioning][semver] specification. Pin to the latest major version to avoid breaking changes (e.g. `15`), avoid using the `latest` tag.
|
||||
|
||||
[github-ci]: https://github.com/wg-easy/wg-easy/actions
|
||||
[ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy
|
||||
[codeberg-image]: https://codeberg.org/wg-easy/-/packages/container/wg-easy/15
|
||||
[semver]: https://semver.org/
|
||||
|
||||
### Follow tutorials
|
||||
@@ -67,6 +70,7 @@ When publishing a tag we follow the [Semantic Versioning][semver] specification.
|
||||
/// 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**.
|
||||
|
||||
@@ -2,4 +2,44 @@
|
||||
title: Admin Panel
|
||||
---
|
||||
|
||||
TODO
|
||||
## Interface Settings
|
||||
|
||||
### Per-Client Firewall
|
||||
|
||||
Enable server-side firewall filtering to enforce network access restrictions per client.
|
||||
|
||||
When enabled, each client can have custom "Firewall Allowed IPs" configured that restrict which destinations they can access through the VPN. These restrictions are enforced by the server using iptables/ip6tables and cannot be bypassed by the client.
|
||||
|
||||
/// warning | Experimental Feature
|
||||
|
||||
This feature is currently experimental. While functional, it should be thoroughly tested in your environment before relying on it for production security requirements. Always verify that firewall rules are working as expected using test traffic or by manually inspecting the rules.
|
||||
|
||||
///
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- `iptables` must be installed on the host system
|
||||
- `ip6tables` must be installed if IPv6 is enabled (default)
|
||||
- The feature cannot be enabled if these tools are not available
|
||||
|
||||
/// note
|
||||
|
||||
Most Linux distributions include iptables by default. If you're running in a minimal container environment, you may need to install the `iptables` package on the host system.
|
||||
|
||||
///
|
||||
|
||||
**Enable this feature if you want to:**
|
||||
|
||||
- Restrict certain clients to only access specific servers or networks
|
||||
- Prevent clients from accessing the internet while allowing LAN access
|
||||
- Enforce port-based restrictions (e.g., only allow HTTP/HTTPS)
|
||||
- Separate routing configuration from security enforcement
|
||||
|
||||
**How it works:**
|
||||
|
||||
1. Enable "Per-Client Firewall" in Admin Panel → Interface
|
||||
2. Edit any client to see the new "Firewall Allowed IPs" field
|
||||
3. Specify allowed destinations (IPs, subnets, ports) for that client
|
||||
4. Server enforces these rules automatically
|
||||
|
||||
See [Edit Client → Firewall Allowed IPs](./clients.md#firewall-allowed-ips) for detailed configuration syntax and examples.
|
||||
|
||||
@@ -41,3 +41,31 @@ 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.
|
||||
|
||||
### Show Clients
|
||||
|
||||
List all clients that are currently configured with details such as client ID, Name, Public Key, and enabled status.
|
||||
|
||||
```shell
|
||||
cli clients:list
|
||||
```
|
||||
|
||||
### Show Client QR Code
|
||||
|
||||
Display the QR code for a specific client, which can be scanned by a compatible app to import the client's configuration.
|
||||
|
||||
```shell
|
||||
cli clients:qr <client_id>
|
||||
```
|
||||
|
||||
Replace `<client_id>` with the actual client ID you want to show the QR code for.
|
||||
|
||||
/// warning | IPv6 Support
|
||||
|
||||
IPv6 support is enabled by default, even if you disabled it using environment variables. To disable it pass the `--no-ipv6` flag when running the CLI.
|
||||
|
||||
```shell
|
||||
cli clients:qr <client_id> --no-ipv6
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
@@ -19,7 +19,60 @@ 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.
|
||||
Use the Firewall Allowed IPs feature to prevent access to IP ranges that the user should not be able to access.
|
||||
|
||||
## Firewall Allowed IPs
|
||||
|
||||
/// note | Attention
|
||||
|
||||
This field only appears when **Per-Client Firewall** is enabled in the Admin Panel → Interface settings.
|
||||
|
||||
///
|
||||
|
||||
Server-side firewall rules that restrict which destinations the client can access, regardless of their local configuration.
|
||||
|
||||
Unlike "Allowed IPs" which only controls routing on the client side, these rules are enforced by the server using iptables/ip6tables and cannot be bypassed by the client.
|
||||
|
||||
**Supported Formats:**
|
||||
|
||||
- `10.10.0.3`, `2001:db8::1` - Allow access to a single IP address
|
||||
- `10.10.0.0/24`, `2001:db8::/32` - Allow access to an entire subnet
|
||||
- `192.168.1.5:443` - Allow access to specific port (TCP+UDP)
|
||||
- `192.168.1.5:443/tcp` - Allow access to specific port (TCP only)
|
||||
- `192.168.1.5:443/udp` - Allow access to specific port (UDP only)
|
||||
- `10.10.0.0/24:443` - Allow access to an entire subnet on a specific port (TCP+UDP)
|
||||
- `10.10.0.0/24:443/tcp` - Allow access to an entire subnet on a specific port (TCP only)
|
||||
- `10.10.0.0/24:443/udp` - Allow access to an entire subnet on a specific port (UDP only)
|
||||
- `[2001:db8::1]:443` - IPv6 address with port (brackets required)
|
||||
- `[2001:db8::/32]:443/tcp` - IPv6 CIDR with port and protocol
|
||||
|
||||
/// warning | Invalid Formats
|
||||
|
||||
Protocol specifiers (`/tcp` or `/udp`) require a port number. The following formats are **not supported** and will result in an error:
|
||||
|
||||
- `10.10.0.3/tcp` (use `10.10.0.3:443/tcp` instead)
|
||||
- `10.10.0.0/24/udp` (use `10.10.0.0/24:53/udp` instead)
|
||||
|
||||
///
|
||||
|
||||
**Behavior:**
|
||||
|
||||
- **Empty**: Falls back to the client's "Allowed IPs" setting
|
||||
- **Specified**: Only listed destinations are accessible (allow-only, everything else is blocked)
|
||||
- **Disable for specific client**: To disable firewall filtering for a single client while keeping it enabled for others, add `0.0.0.0/0, ::/0` to allow all traffic
|
||||
|
||||
/// note
|
||||
|
||||
To allow clients to reach the VPN server itself (e.g. for DNS), include the server's VPN address in the firewall allowed IPs.
|
||||
|
||||
///
|
||||
|
||||
**Use Case Examples**:
|
||||
|
||||
- Allow only specific servers: `10.10.0.5`
|
||||
- Allow only internal network: `10.10.0.0/24, 192.168.1.0/24`
|
||||
- Allow only web browsing: `0.0.0.0/0:80, 0.0.0.0/0:443, [::/0]:80, [::/0]:443`
|
||||
- Block internet, allow LAN: Leave "Allowed IPs" as `0.0.0.0/0, ::/0` but set Firewall IPs to `10.0.0.0/8, 192.168.0.0/16`
|
||||
|
||||
## Server Allowed IPs
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ hide:
|
||||
|
||||
/// info | This Documentation is Versioned
|
||||
|
||||
**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].
|
||||
**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 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.
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
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>
|
||||
|
||||
repo_url: https://github.com/wg-easy/wg-easy
|
||||
repo_name: wg-easy
|
||||
|
||||
edit_uri: 'edit/master/docs/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
|
||||
|
||||
extra:
|
||||
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
|
||||
@@ -1,4 +1,2 @@
|
||||
mkdocs-material
|
||||
pillow
|
||||
cairosvg
|
||||
mike
|
||||
zensical==0.0.45
|
||||
git+https://github.com/squidfunk/mike.git
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
[project]
|
||||
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>
|
||||
"""
|
||||
|
||||
repo_url = "https://github.com/wg-easy/wg-easy"
|
||||
repo_name = "wg-easy"
|
||||
|
||||
edit_uri = "edit/master/docs/content"
|
||||
|
||||
docs_dir = "content/"
|
||||
|
||||
site_url = "https://wg-easy.github.io/wg-easy"
|
||||
|
||||
[project.theme]
|
||||
variant = "modern"
|
||||
favicon = "assets/logo/favicon.png"
|
||||
logo = "assets/logo/logo.png"
|
||||
features = [
|
||||
"navigation.instant",
|
||||
"navigation.tabs",
|
||||
"navigation.expand",
|
||||
"navigation.path",
|
||||
"navigation.top",
|
||||
"content.action.edit",
|
||||
"content.action.view",
|
||||
"content.code.copy",
|
||||
"content.code.annotate",
|
||||
]
|
||||
|
||||
[project.theme.icon]
|
||||
repo = "fontawesome/brands/github"
|
||||
|
||||
# Palette toggle for automatic mode
|
||||
[[project.theme.palette]]
|
||||
media = "(prefers-color-scheme)"
|
||||
toggle.icon = "lucide/sun-moon"
|
||||
toggle.name = "Switch to light mode"
|
||||
|
||||
# Palette toggle for light mode
|
||||
[[project.theme.palette]]
|
||||
media = "(prefers-color-scheme: light)"
|
||||
scheme = "default"
|
||||
primary = "grey"
|
||||
accent = "red"
|
||||
toggle.icon = "lucide/sun"
|
||||
toggle.name = "Switch to dark mode"
|
||||
|
||||
# Palette toggle for dark mode
|
||||
[[project.theme.palette]]
|
||||
media = "(prefers-color-scheme: dark)"
|
||||
scheme = "slate"
|
||||
primary = "grey"
|
||||
accent = "red"
|
||||
toggle.icon = "lucide/moon"
|
||||
toggle.name = "Switch to system preference"
|
||||
|
||||
# [project.plugins.privacy]
|
||||
|
||||
[project.extra.version]
|
||||
provider = "mike"
|
||||
|
||||
[project.markdown_extensions.pymdownx.blocks.admonition]
|
||||
types = ["danger", "note", "info", "question", "warning"]
|
||||
|
||||
[project.markdown_extensions.md_in_html]
|
||||
|
||||
[project.markdown_extensions.toc]
|
||||
permalink = true
|
||||
|
||||
[project.markdown_extensions.pymdownx.details]
|
||||
|
||||
[project.markdown_extensions.pymdownx.emoji]
|
||||
emoji_generator = "zensical.extensions.emoji.to_svg"
|
||||
emoji_index = "zensical.extensions.emoji.twemoji"
|
||||
|
||||
[project.markdown_extensions.pymdownx.highlight]
|
||||
anchor_linenums = true
|
||||
line_spans = "__span"
|
||||
pygments_lang_class = true
|
||||
|
||||
[project.markdown_extensions.pymdownx.inlinehilite]
|
||||
|
||||
[project.markdown_extensions.pymdownx.smartsymbols]
|
||||
|
||||
[project.markdown_extensions.pymdownx.superfences]
|
||||
custom_fences = [
|
||||
{ name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" },
|
||||
]
|
||||
+4
-3
@@ -5,12 +5,13 @@
|
||||
"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 zensical/zensical serve -a 0.0.0.0:8080",
|
||||
"scripts:version": "bash scripts/version.sh",
|
||||
"scripts:i18n": "bash scripts/i18n.sh",
|
||||
"format:check:docs": "prettier --check docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.3"
|
||||
"prettier": "^3.9.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.11.0"
|
||||
"packageManager": "pnpm@11.15.1"
|
||||
}
|
||||
|
||||
Generated
+5
-5
@@ -9,16 +9,16 @@ importers:
|
||||
.:
|
||||
devDependencies:
|
||||
prettier:
|
||||
specifier: ^3.5.3
|
||||
version: 3.5.3
|
||||
specifier: ^3.9.5
|
||||
version: 3.9.5
|
||||
|
||||
packages:
|
||||
|
||||
prettier@3.5.3:
|
||||
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
|
||||
prettier@3.9.5:
|
||||
resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
prettier@3.5.3: {}
|
||||
prettier@3.9.5: {}
|
||||
|
||||
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
|
||||
@@ -30,6 +30,7 @@ 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 "Make sure to stage any changes before proceeding (e.g. Changelog updates)."
|
||||
echo "----"
|
||||
|
||||
echo "If you did everything press 'y' to commit the changes and create a new tag"
|
||||
|
||||
@@ -23,4 +23,6 @@ logs
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
coverage/
|
||||
|
||||
wg-easy.db
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
setups.@nuxt/test-utils="4.0.3"
|
||||
@@ -0,0 +1,7 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -18,7 +18,7 @@
|
||||
>
|
||||
<slot name="description" />
|
||||
</DialogDescription>
|
||||
<div class="mt-6 flex justify-end gap-2">
|
||||
<div class="mt-6 flex flex-wrap justify-end gap-2">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<component
|
||||
:is="elementType"
|
||||
role="button"
|
||||
class="rounded-lg border-2 border-gray-100 text-gray-500 hover:border-red-800 hover:bg-red-800 hover:text-white focus:border-red-800 focus:outline-0 focus:ring-0 disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-100 disabled:text-gray-400 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400 dark:disabled:border-neutral-800 dark:disabled:bg-neutral-700 dark:disabled:text-neutral-400"
|
||||
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, ...rest } = props;
|
||||
return rest;
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<textarea
|
||||
v-model="data"
|
||||
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"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const data = defineModel<string>();
|
||||
</script>
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ApexOptions } from 'apexcharts';
|
||||
import type { ApexChart, ApexOptions } from 'apexcharts';
|
||||
|
||||
defineProps<{
|
||||
client: LocalClient;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ClientCardCharts :client="client" />
|
||||
<div
|
||||
class="relative z-10 flex flex-col justify-between gap-3 px-3 py-3 sm:flex-row md:py-5"
|
||||
class="relative flex flex-col justify-between gap-3 px-3 py-3 sm:flex-row md:py-5"
|
||||
>
|
||||
<div class="flex w-full items-center gap-3 md:gap-4">
|
||||
<ClientCardAvatar :client="client" />
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="flex flex-grow flex-col gap-1">
|
||||
<ClientCardName :client="client" />
|
||||
<div
|
||||
class="flex flex-col pb-1 text-xs text-gray-500 md:inline-block md:pb-0 dark:text-neutral-400"
|
||||
class="flex flex-col text-xs text-gray-500 dark:text-neutral-400"
|
||||
>
|
||||
<div>
|
||||
<ClientCardAddress :client="client" />
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<div
|
||||
class="block pb-1 text-xs text-gray-500 md:inline-block md:pb-0 dark:text-neutral-400"
|
||||
>
|
||||
<div class="block text-xs text-gray-500 dark:text-neutral-400">
|
||||
<span class="inline-block">{{ expiredDateFormat(client.expiresAt) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<template>
|
||||
<div
|
||||
class="text-sm text-gray-700 md:text-base dark:text-neutral-200"
|
||||
class="break-all text-sm text-gray-700 md:text-base dark:text-neutral-200"
|
||||
:title="$t('client.createdOn') + $d(new Date(client.createdAt))"
|
||||
>
|
||||
<span class="border-b-2 border-t-2 border-transparent">
|
||||
{{ client.name }}
|
||||
</span>
|
||||
{{ client.name }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -14,10 +14,11 @@ const props = defineProps<{ client: LocalClient }>();
|
||||
const clientsStore = useClientsStore();
|
||||
|
||||
const _showOneTimeLink = useSubmit(
|
||||
`/api/client/${props.client.id}/generateOneTimeLink`,
|
||||
{
|
||||
method: 'post',
|
||||
},
|
||||
(data) =>
|
||||
$fetch(`/api/client/${props.client.id}/generateOneTimeLink`, {
|
||||
method: 'post',
|
||||
body: data,
|
||||
}),
|
||||
{
|
||||
revert: async () => {
|
||||
await clientsStore.refresh();
|
||||
|
||||
@@ -18,10 +18,11 @@ const enabled = ref(props.client.enabled);
|
||||
const clientsStore = useClientsStore();
|
||||
|
||||
const _disableClient = useSubmit(
|
||||
`/api/client/${props.client.id}/disable`,
|
||||
{
|
||||
method: 'post',
|
||||
},
|
||||
(data) =>
|
||||
$fetch(`/api/client/${props.client.id}/disable`, {
|
||||
method: 'post',
|
||||
body: data,
|
||||
}),
|
||||
{
|
||||
revert: async () => {
|
||||
await clientsStore.refresh();
|
||||
@@ -31,10 +32,11 @@ const _disableClient = useSubmit(
|
||||
);
|
||||
|
||||
const _enableClient = useSubmit(
|
||||
`/api/client/${props.client.id}/enable`,
|
||||
{
|
||||
method: 'post',
|
||||
},
|
||||
(data) =>
|
||||
$fetch(`/api/client/${props.client.id}/enable`, {
|
||||
method: 'post',
|
||||
body: data,
|
||||
}),
|
||||
{
|
||||
revert: async () => {
|
||||
await clientsStore.refresh();
|
||||
|
||||
@@ -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>
|
||||
@@ -43,10 +43,11 @@ function createClient() {
|
||||
}
|
||||
|
||||
const _createClient = useSubmit(
|
||||
'/api/client',
|
||||
{
|
||||
method: 'post',
|
||||
},
|
||||
(data) =>
|
||||
$fetch('/api/client', {
|
||||
method: 'post',
|
||||
body: data,
|
||||
}),
|
||||
{
|
||||
revert: () => clientsStore.refresh(),
|
||||
successMsg: t('client.created'),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{{ $t('client.empty') }}<br /><br />
|
||||
<ClientsCreateDialog>
|
||||
<BaseSecondaryButton as="span">
|
||||
<IconsPlus class="w-4 md:mr-2" />
|
||||
<IconsPlus class="mr-2 w-4" />
|
||||
<span class="text-sm">{{ $t('client.new') }}</span>
|
||||
</BaseSecondaryButton>
|
||||
</ClientsCreateDialog>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<ClientsCreateDialog>
|
||||
<BaseSecondaryButton as="span">
|
||||
<IconsPlus class="w-4 md:mr-2" />
|
||||
<span class="text-sm max-md:hidden">{{ $t('client.newShort') }}</span>
|
||||
<IconsPlus class="mr-2 w-4" />
|
||||
<span class="text-sm">{{ $t('client.newShort') }}</span>
|
||||
</BaseSecondaryButton>
|
||||
</ClientsCreateDialog>
|
||||
</template>
|
||||
|
||||
@@ -5,11 +5,25 @@
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="bg-white">
|
||||
<img :src="qrCode" />
|
||||
<img ref="img" :src="qrCode" />
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<DialogClose>
|
||||
<BaseSecondaryButton
|
||||
class="flex items-center gap-2"
|
||||
:title="$t('client.copyPng')"
|
||||
@click="copyPng"
|
||||
>
|
||||
<IconsCopy class="size-5" /> PNG
|
||||
</BaseSecondaryButton>
|
||||
<BaseSecondaryButton
|
||||
class="flex items-center gap-2"
|
||||
:title="$t('client.downloadPng')"
|
||||
@click="downloadPng"
|
||||
>
|
||||
<IconsDownload class="size-5" /> PNG
|
||||
</BaseSecondaryButton>
|
||||
<DialogClose as-child>
|
||||
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
|
||||
</DialogClose>
|
||||
</template>
|
||||
@@ -18,4 +32,87 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ qrCode: string }>();
|
||||
|
||||
const toast = useToast();
|
||||
const img = useTemplateRef('img');
|
||||
|
||||
async function svgToPng() {
|
||||
if (!img.value || !img.value.complete || img.value.naturalWidth === 0) {
|
||||
throw new Error('image is not loaded');
|
||||
}
|
||||
|
||||
const width = 1000;
|
||||
const height = 1000;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
throw new Error('was not able to create 2d context');
|
||||
}
|
||||
ctx.drawImage(img.value!, 0, 0, width, height);
|
||||
|
||||
return new Promise<Blob>((res, rej) => {
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) {
|
||||
return rej(new Error('was not able to create blob'));
|
||||
}
|
||||
return res(blob);
|
||||
}, 'image/png');
|
||||
});
|
||||
}
|
||||
|
||||
async function downloadPng() {
|
||||
try {
|
||||
const blob = await svgToPng();
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'client-config.png';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (e) {
|
||||
console.error('failed to download png', e);
|
||||
toast.showToast({
|
||||
type: 'error',
|
||||
message: $t('toast.unknown'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function copyPng() {
|
||||
const blob = await svgToPng().catch((e) => {
|
||||
console.error('failed to convert svg to png', e);
|
||||
toast.showToast({
|
||||
type: 'error',
|
||||
message: $t('toast.unknown'),
|
||||
});
|
||||
});
|
||||
if (!blob) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
[blob.type]: blob,
|
||||
}),
|
||||
]);
|
||||
|
||||
toast.showToast({
|
||||
type: 'success',
|
||||
message: $t('copy.copied'),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('failed to copy png', e);
|
||||
toast.showToast({
|
||||
type: 'error',
|
||||
message: $t('copy.failed'),
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div class="relative flex h-full items-center">
|
||||
<IconsMagnifyingGlass
|
||||
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 px-8 py-2 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,11 +1,8 @@
|
||||
<template>
|
||||
<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>
|
||||
<IconsArrowDown v-if="globalStore.sortClient === 'asc'" class="mr-2 w-4" />
|
||||
<IconsArrowUp v-else class="mr-2 w-4" />
|
||||
<span class="text-sm">{{ $t('client.sort') }}</span>
|
||||
</BasePrimaryButton>
|
||||
</template>
|
||||
|
||||
@@ -14,7 +11,7 @@ const globalStore = useGlobalStore();
|
||||
const clientsStore = useClientsStore();
|
||||
|
||||
function toggleSort() {
|
||||
globalStore.sortClient = !globalStore.sortClient;
|
||||
globalStore.sortClient = globalStore.sortClient === 'asc' ? 'desc' : 'asc';
|
||||
clientsStore.refresh().catch(console.error);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -12,23 +12,15 @@
|
||||
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)"
|
||||
/>
|
||||
<BaseSecondaryButton type="button" class="rounded-lg" @click="del(i)">
|
||||
{{ '-' }}
|
||||
</BaseSecondaryButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<BasePrimaryButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
:value="$t('form.add')"
|
||||
@click="add"
|
||||
/>
|
||||
<BasePrimaryButton type="button" class="rounded-lg" @click="add">
|
||||
{{ $t('form.add') }}
|
||||
</BasePrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<h4 class="col-span-full flex items-center py-6 text-2xl">
|
||||
<h3 class="col-span-full flex items-center py-6 text-2xl">
|
||||
<slot />
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
</h4>
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
<BasePrimaryButton as="span">
|
||||
<div class="flex items-center gap-3">
|
||||
<IconsSparkles class="w-4" />
|
||||
<span>{{ $t('admin.config.suggest') }}</span>
|
||||
<span class="whitespace-nowrap">
|
||||
{{ $t('admin.config.suggest') }}
|
||||
</span>
|
||||
</div>
|
||||
</BasePrimaryButton>
|
||||
</AdminSuggestDialog>
|
||||
|
||||
@@ -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>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<RLabel :for="props.for" class="md:align-middle md:leading-10"
|
||||
><slot
|
||||
/></RLabel>
|
||||
<RLabel :for="props.for" class="md:leading-[2.75rem]">
|
||||
<slot />
|
||||
</RLabel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -12,23 +12,15 @@
|
||||
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)"
|
||||
/>
|
||||
<BaseSecondaryButton type="button" class="rounded-lg" @click="del(i)">
|
||||
{{ '-' }}
|
||||
</BaseSecondaryButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<BasePrimaryButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
:value="$t('form.add')"
|
||||
@click="add"
|
||||
/>
|
||||
<BasePrimaryButton type="button" class="rounded-lg" @click="add">
|
||||
{{ $t('form.add') }}
|
||||
</BasePrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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>
|
||||
@@ -2,7 +2,7 @@
|
||||
<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"
|
||||
class="col-span-2 rounded-lg border-2 border-red-800 bg-red-800 px-4 py-2 text-white hover:border-red-600 hover:bg-red-600 focus:border-red-800 focus:outline-0 focus:ring-0"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
<template>
|
||||
<input
|
||||
:value="label"
|
||||
:type="type ?? 'button'"
|
||||
class="col-span-2 rounded-lg border-2 border-gray-100 py-2 text-gray-500 hover:border-red-800 hover:bg-red-800 hover:text-white 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"
|
||||
/>
|
||||
<BaseFormSecondaryButton
|
||||
:as="as"
|
||||
:value="isInput ? label : undefined"
|
||||
:type="isInput ? type : undefined"
|
||||
class="col-span-2 px-4 py-2"
|
||||
>
|
||||
<template v-if="!isInput">
|
||||
{{ label }}
|
||||
</template>
|
||||
</BaseFormSecondaryButton>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { InputTypeHTMLAttribute } from 'vue';
|
||||
|
||||
defineProps<{
|
||||
label: string;
|
||||
type?: InputTypeHTMLAttribute;
|
||||
}>();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label: string;
|
||||
type?: InputTypeHTMLAttribute;
|
||||
as?: string;
|
||||
}>(),
|
||||
{
|
||||
type: 'button',
|
||||
as: 'input',
|
||||
}
|
||||
);
|
||||
|
||||
const isInput = computed(() => {
|
||||
return props.as === 'input';
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<BaseSwitch :id="id" v-model="data" />
|
||||
<div class="my-auto">
|
||||
<BaseSwitch :id="id" v-model="data" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<FormLabel :for="id">
|
||||
{{ label }}
|
||||
</FormLabel>
|
||||
<BaseTooltip v-if="description" :text="description">
|
||||
<IconsInfo class="size-4" />
|
||||
</BaseTooltip>
|
||||
</div>
|
||||
<BaseTextArea
|
||||
:id="id"
|
||||
v-model.trim="data"
|
||||
:name="id"
|
||||
:autocomplete="autocomplete"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
autocomplete?: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
const data = defineModel<string>();
|
||||
</script>
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<Toggle
|
||||
:pressed="globalStore.uiShowCharts"
|
||||
class="group inline-flex h-8 w-8 cursor-pointer items-center justify-center whitespace-nowrap rounded-full bg-gray-200 transition hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600"
|
||||
class="group flex h-8 w-8 items-center justify-center rounded-full bg-gray-200 transition hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600"
|
||||
:title="$t('layout.toggleCharts')"
|
||||
@update:pressed="globalStore.toggleCharts"
|
||||
>
|
||||
<IconsChart
|
||||
class="h-5 w-5 fill-gray-400 transition group-data-[state=on]:fill-gray-600 dark:fill-neutral-600 dark:group-data-[state=on]:fill-neutral-400"
|
||||
class="h-5 w-5 transition group-data-[state=on]:fill-gray-600 dark:text-neutral-400 dark:group-data-[state=on]:fill-gray-300"
|
||||
/>
|
||||
</Toggle>
|
||||
</template>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<SelectRoot v-model="langProxy" :default-value="locale">
|
||||
<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-700 dark:text-neutral-400"
|
||||
class="group inline-flex h-8 items-center justify-around gap-2 rounded bg-gray-200 px-3 text-sm leading-none dark:bg-neutral-700 dark:text-neutral-400"
|
||||
aria-label="Select language"
|
||||
>
|
||||
<IconsLanguage class="size-3" />
|
||||
<SelectValue />
|
||||
<IconsArrowDown class="size-3" />
|
||||
<IconsArrowDown
|
||||
class="size-3 transition-transform group-data-[state=open]:rotate-180"
|
||||
/>
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectPortal>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<NuxtLink to="/" class="mb-4">
|
||||
<NuxtLink to="/" class="max-sm:mb-4">
|
||||
<h1 class="text-4xl font-medium dark:text-neutral-200">
|
||||
<img
|
||||
src="/logo.png"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
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"
|
||||
class="font-small rounded-md bg-red-800 p-4 text-sm text-white shadow-lg dark:bg-red-100 dark:text-red-600"
|
||||
:title="`v${globalStore.information.currentRelease} → v${globalStore.information.latestRelease.version}`"
|
||||
>
|
||||
<div class="container mx-auto flex flex-auto flex-row items-center">
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<!-- https://brand.github.com/foundations/logo -->
|
||||
<svg
|
||||
width="98"
|
||||
height="96"
|
||||
viewBox="0 0 98 96"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_730_27136)">
|
||||
<path
|
||||
d="M41.4395 69.3848C28.8066 67.8535 19.9062 58.7617 19.9062 46.9902C19.9062 42.2051 21.6289 37.0371 24.5 33.5918C23.2559 30.4336 23.4473 23.7344 24.8828 20.959C28.7109 20.4805 33.8789 22.4902 36.9414 25.2656C40.5781 24.1172 44.4062 23.543 49.0957 23.543C53.7852 23.543 57.6133 24.1172 61.0586 25.1699C64.0254 22.4902 69.2891 20.4805 73.1172 20.959C74.457 23.543 74.6484 30.2422 73.4043 33.4961C76.4668 37.1328 78.0937 42.0137 78.0937 46.9902C78.0937 58.7617 69.1934 67.6621 56.3691 69.2891C59.623 71.3945 61.8242 75.9883 61.8242 81.252L61.8242 91.2051C61.8242 94.0762 64.2168 95.7031 67.0879 94.5547C84.4102 87.9512 98 70.6289 98 49.1914C98 22.1074 75.9883 6.69539e-07 48.9043 4.309e-07C21.8203 1.92261e-07 -1.9479e-07 22.1074 -4.3343e-07 49.1914C-6.20631e-07 70.4375 13.4941 88.0469 31.6777 94.6504C34.2617 95.6074 36.75 93.8848 36.75 91.3008L36.75 83.6445C35.4102 84.2188 33.6875 84.6016 32.1562 84.6016C25.8398 84.6016 22.1074 81.1563 19.4277 74.7441C18.375 72.1602 17.2266 70.6289 15.0254 70.3418C13.877 70.2461 13.4941 69.7676 13.4941 69.1934C13.4941 68.0449 15.4082 67.1836 17.3223 67.1836C20.0977 67.1836 22.4902 68.9063 24.9785 72.4473C26.8926 75.2227 28.9023 76.4668 31.2949 76.4668C33.6875 76.4668 35.2187 75.6055 37.4199 73.4043C39.0469 71.7773 40.291 70.3418 41.4395 69.3848Z"
|
||||
fill="white"
|
||||
class="fill-black dark:fill-white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_730_27136">
|
||||
<rect width="98" height="96" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<!-- By Original: Google Vector: Designism, Liquinoid, and YeBoy371 - Own work based on: Google updating its ‘G’ icon for the first time in 10 years, Public Domain, https://commons.wikimedia.org/w/index.php?curid=165142698 -->
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="0 0 268.1522 273.8827"
|
||||
overflow="hidden"
|
||||
xml:space="preserve"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="a">
|
||||
<stop offset="0" stop-color="#0fbc5c" />
|
||||
<stop offset="1" stop-color="#0cba65" />
|
||||
</linearGradient>
|
||||
<linearGradient id="g">
|
||||
<stop offset=".2312727" stop-color="#0fbc5f" />
|
||||
<stop offset=".3115468" stop-color="#0fbc5f" />
|
||||
<stop offset=".3660131" stop-color="#0fbc5e" />
|
||||
<stop offset=".4575163" stop-color="#0fbc5d" />
|
||||
<stop offset=".540305" stop-color="#12bc58" />
|
||||
<stop offset=".6993464" stop-color="#28bf3c" />
|
||||
<stop offset=".7712418" stop-color="#38c02b" />
|
||||
<stop offset=".8605665" stop-color="#52c218" />
|
||||
<stop offset=".9150327" stop-color="#67c30f" />
|
||||
<stop offset="1" stop-color="#86c504" />
|
||||
</linearGradient>
|
||||
<linearGradient id="h">
|
||||
<stop offset=".1416122" stop-color="#1abd4d" />
|
||||
<stop offset=".2475151" stop-color="#6ec30d" />
|
||||
<stop offset=".3115468" stop-color="#8ac502" />
|
||||
<stop offset=".3660131" stop-color="#a2c600" />
|
||||
<stop offset=".4456735" stop-color="#c8c903" />
|
||||
<stop offset=".540305" stop-color="#ebcb03" />
|
||||
<stop offset=".6156363" stop-color="#f7cd07" />
|
||||
<stop offset=".6993454" stop-color="#fdcd04" />
|
||||
<stop offset=".7712418" stop-color="#fdce05" />
|
||||
<stop offset=".8605661" stop-color="#ffce0a" />
|
||||
</linearGradient>
|
||||
<linearGradient id="f">
|
||||
<stop offset=".3159041" stop-color="#ff4c3c" />
|
||||
<stop offset=".6038179" stop-color="#ff692c" />
|
||||
<stop offset=".7268366" stop-color="#ff7825" />
|
||||
<stop offset=".884534" stop-color="#ff8d1b" />
|
||||
<stop offset="1" stop-color="#ff9f13" />
|
||||
</linearGradient>
|
||||
<linearGradient id="b">
|
||||
<stop offset=".2312727" stop-color="#ff4541" />
|
||||
<stop offset=".3115468" stop-color="#ff4540" />
|
||||
<stop offset=".4575163" stop-color="#ff4640" />
|
||||
<stop offset=".540305" stop-color="#ff473f" />
|
||||
<stop offset=".6993464" stop-color="#ff5138" />
|
||||
<stop offset=".7712418" stop-color="#ff5b33" />
|
||||
<stop offset=".8605665" stop-color="#ff6c29" />
|
||||
<stop offset="1" stop-color="#ff8c18" />
|
||||
</linearGradient>
|
||||
<linearGradient id="d">
|
||||
<stop offset=".4084578" stop-color="#fb4e5a" />
|
||||
<stop offset="1" stop-color="#ff4540" />
|
||||
</linearGradient>
|
||||
<linearGradient id="c">
|
||||
<stop offset=".1315461" stop-color="#0cba65" />
|
||||
<stop offset=".2097843" stop-color="#0bb86d" />
|
||||
<stop offset=".2972969" stop-color="#09b479" />
|
||||
<stop offset=".3962575" stop-color="#08ad93" />
|
||||
<stop offset=".4771242" stop-color="#0aa6a9" />
|
||||
<stop offset=".5684245" stop-color="#0d9cc6" />
|
||||
<stop offset=".667385" stop-color="#1893dd" />
|
||||
<stop offset=".7687273" stop-color="#258bf1" />
|
||||
<stop offset=".8585063" stop-color="#3086ff" />
|
||||
</linearGradient>
|
||||
<linearGradient id="e">
|
||||
<stop offset=".3660131" stop-color="#ff4e3a" />
|
||||
<stop offset=".4575163" stop-color="#ff8a1b" />
|
||||
<stop offset=".540305" stop-color="#ffa312" />
|
||||
<stop offset=".6156363" stop-color="#ffb60c" />
|
||||
<stop offset=".7712418" stop-color="#ffcd0a" />
|
||||
<stop offset=".8605665" stop-color="#fecf0a" />
|
||||
<stop offset=".9150327" stop-color="#fecf08" />
|
||||
<stop offset="1" stop-color="#fdcd01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="s"
|
||||
xlink:href="#a"
|
||||
x1="219.6997"
|
||||
y1="329.5351"
|
||||
x2="254.4673"
|
||||
y2="329.5351"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
/>
|
||||
<radialGradient
|
||||
id="m"
|
||||
xlink:href="#b"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.936885,1.043001,1.455731,2.555422,290.5254,-400.6338)"
|
||||
cx="109.6267"
|
||||
cy="135.8619"
|
||||
fx="109.6267"
|
||||
fy="135.8619"
|
||||
r="71.46001"
|
||||
/>
|
||||
<radialGradient
|
||||
id="n"
|
||||
xlink:href="#c"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-3.512595,-4.45809,-1.692547,1.260616,870.8006,191.554)"
|
||||
cx="45.25866"
|
||||
cy="279.2738"
|
||||
fx="45.25866"
|
||||
fy="279.2738"
|
||||
r="71.46001"
|
||||
/>
|
||||
<radialGradient
|
||||
id="l"
|
||||
xlink:href="#d"
|
||||
cx="304.0166"
|
||||
cy="118.0089"
|
||||
fx="304.0166"
|
||||
fy="118.0089"
|
||||
r="47.85445"
|
||||
gradientTransform="matrix(2.064353,-4.926832e-6,-2.901531e-6,2.592041,-297.6788,-151.7469)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
/>
|
||||
<radialGradient
|
||||
id="o"
|
||||
xlink:href="#e"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-0.2485783,2.083138,2.962486,0.3341668,-255.1463,-331.1636)"
|
||||
cx="181.001"
|
||||
cy="177.2013"
|
||||
fx="181.001"
|
||||
fy="177.2013"
|
||||
r="71.46001"
|
||||
/>
|
||||
<radialGradient
|
||||
id="p"
|
||||
xlink:href="#f"
|
||||
cx="207.6733"
|
||||
cy="108.0972"
|
||||
fx="207.6733"
|
||||
fy="108.0972"
|
||||
r="41.1025"
|
||||
gradientTransform="matrix(-1.249206,1.343263,-3.896837,-3.425693,880.5011,194.9051)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
/>
|
||||
<radialGradient
|
||||
id="r"
|
||||
xlink:href="#g"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.936885,-1.043001,1.455731,-2.555422,290.5254,838.6834)"
|
||||
cx="109.6267"
|
||||
cy="135.8619"
|
||||
fx="109.6267"
|
||||
fy="135.8619"
|
||||
r="71.46001"
|
||||
/>
|
||||
<radialGradient
|
||||
id="j"
|
||||
xlink:href="#h"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-0.081402,-1.93722,2.926737,-0.1162508,-215.1345,632.8606)"
|
||||
cx="154.8697"
|
||||
cy="145.9691"
|
||||
fx="154.8697"
|
||||
fy="145.9691"
|
||||
r="71.46001"
|
||||
/>
|
||||
<filter
|
||||
id="q"
|
||||
x="-.04842873"
|
||||
y="-.0582241"
|
||||
width="1.096857"
|
||||
height="1.116448"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feGaussianBlur stdDeviation="1.700914" />
|
||||
</filter>
|
||||
<filter
|
||||
id="k"
|
||||
x="-.01670084"
|
||||
y="-.01009856"
|
||||
width="1.033402"
|
||||
height="1.020197"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feGaussianBlur stdDeviation=".2419367" />
|
||||
</filter>
|
||||
<clipPath id="i" clipPathUnits="userSpaceOnUse">
|
||||
<path
|
||||
d="M371.3784 193.2406H237.0825v53.4375h77.167c-1.2405 7.5627-4.0259 15.0024-8.1049 21.7862-4.6734 7.7723-10.4511 13.6895-16.373 18.1957-17.7389 13.4983-38.42 16.2584-52.7828 16.2584-36.2824 0-67.2833-23.2865-79.2844-54.9287-.4843-1.1482-.8059-2.3344-1.1975-3.5068-2.652-8.0533-4.101-16.5825-4.101-25.4474 0-9.226 1.5691-18.0575 4.4301-26.3985 11.2851-32.8967 42.9849-57.4674 80.1789-57.4674 7.4811 0 14.6854.8843 21.5173 2.6481 15.6135 4.0309 26.6578 11.9698 33.4252 18.2494l40.834-39.7111c-24.839-22.616-57.2194-36.3201-95.8444-36.3201-30.8782-.00066-59.3863 9.55308-82.7477 25.6992-18.9454 13.0941-34.4833 30.6254-44.9695 50.9861-9.75366 18.8785-15.09441 39.7994-15.09441 62.2934 0 22.495 5.34891 43.6334 15.10261 62.3374v.126c10.3023 19.8567 25.3678 36.9537 43.6783 49.9878 15.9962 11.3866 44.6789 26.5516 84.0307 26.5516 22.6301 0 42.6867-4.0517 60.3748-11.6447 12.76-5.4775 24.0655-12.6217 34.3012-21.8036 13.5247-12.1323 24.1168-27.1388 31.3465-44.4041 7.2297-17.2654 11.097-36.7895 11.097-57.957 0-9.858-.9971-19.8694-2.6881-28.9684Z"
|
||||
fill="#000"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g transform="matrix(0.957922,0,0,0.985255,-90.17436,-78.85577)">
|
||||
<g clip-path="url(#i)">
|
||||
<path
|
||||
d="M92.07563 219.9585c.14844 22.14 6.5014 44.983 16.11767 63.4234v.1269c6.9482 13.3919 16.4444 23.9704 27.2604 34.4518l65.326-23.67c-12.3593-6.2344-14.2452-10.0546-23.1048-17.0253-9.0537-9.0658-15.8015-19.4735-20.0038-31.677h-.1693l.1693-.1269c-2.7646-8.0587-3.0373-16.6129-3.1393-25.5029Z"
|
||||
fill="url(#j)"
|
||||
filter="url(#k)"
|
||||
/>
|
||||
<path
|
||||
d="M237.0835 79.02491c-6.4568 22.52569-3.988 44.42139 0 57.16129 7.4561.0055 14.6388.8881 21.4494 2.6464 15.6135 4.0309 26.6566 11.97 33.424 18.2496l41.8794-40.7256c-24.8094-22.58904-54.6663-37.2961-96.7528-37.33169Z"
|
||||
fill="url(#l)"
|
||||
filter="url(#k)"
|
||||
/>
|
||||
<path
|
||||
d="M236.9434 78.84678c-31.6709-.00068-60.9107 9.79833-84.8718 26.35902-8.8968 6.149-17.0612 13.2521-24.3311 21.1509-1.9045 17.7429 14.2569 39.5507 46.2615 39.3702 15.5284-17.9373 38.4946-29.5427 64.0561-29.5427.0233 0 .046.0019.0693.002l-1.0439-57.33536c-.0472-.00003-.0929-.00406-.1401-.00406Z"
|
||||
fill="url(#m)"
|
||||
filter="url(#k)"
|
||||
/>
|
||||
<path
|
||||
d="m341.4751 226.3788-28.2685 19.2848c-1.2405 7.5627-4.0278 15.0023-8.1068 21.7861-4.6734 7.7723-10.4506 13.6898-16.3725 18.196-17.7022 13.4704-38.3286 16.2439-52.6877 16.2553-14.8415 25.1018-17.4435 37.6749 1.0439 57.9342 22.8762-.0167 43.157-4.1174 61.0458-11.7965 12.9312-5.551 24.3879-12.7913 34.7609-22.0964 13.7061-12.295 24.4421-27.5034 31.7688-45.0003 7.3267-17.497 11.2446-37.2822 11.2446-58.7336Z"
|
||||
fill="url(#n)"
|
||||
filter="url(#k)"
|
||||
/>
|
||||
<path
|
||||
d="M234.9956 191.2104v57.4981h136.0062c1.1962-7.8745 5.1523-18.0644 5.1523-26.5001 0-9.858-.9963-21.899-2.6873-30.998Z"
|
||||
fill="#3086ff"
|
||||
filter="url(#k)"
|
||||
/>
|
||||
<path
|
||||
d="M128.3894 124.3268c-8.393 9.1191-15.5632 19.326-21.2483 30.3646-9.75351 18.8785-15.09402 41.8295-15.09402 64.3235 0 .317.02642.6271.02855.9436 4.31953 8.2244 59.66647 6.6495 62.45617 0-.0035-.3103-.0387-.6128-.0387-.9238 0-9.226 1.5696-16.0262 4.4306-24.3672 3.5294-10.2885 9.0557-19.7628 16.1223-27.9257 1.6019-2.0309 5.8748-6.3969 7.1214-9.0157.4749-.9975-.8621-1.5574-.9369-1.9085-.0836-.3927-1.8762-.0769-2.2778-.3694-1.2751-.9288-3.8001-1.4138-5.3334-1.8449-3.2772-.9215-8.7085-2.9536-11.7252-5.0601-9.5357-6.6586-24.417-14.6122-33.5047-24.2164Z"
|
||||
fill="url(#o)"
|
||||
filter="url(#k)"
|
||||
/>
|
||||
<path
|
||||
d="M162.0989 155.8569c22.1123 13.3013 28.4714-6.7139 43.173-12.9771L179.698 90.21568c-9.4075 3.92642-18.2957 8.80465-26.5426 14.50442-12.316 8.5122-23.192 18.8995-32.1763 30.7204Z"
|
||||
fill="url(#p)"
|
||||
filter="url(#q)"
|
||||
/>
|
||||
<path
|
||||
d="M171.0987 290.222c-29.6829 10.6413-34.3299 11.023-37.0622 29.2903 5.2213 5.0597 10.8312 9.74 16.7926 13.9835 15.9962 11.3867 46.766 26.5517 86.1178 26.5517.0462 0 .0904-.004.1366-.004v-59.1574c-.0298.0001-.064.002-.0938.002-14.7359 0-26.5113-3.8435-38.5848-10.5273-2.9768-1.6479-8.3775 2.7772-11.1229.799-3.7865-2.7284-12.8991 2.3508-16.1833-.9378Z"
|
||||
fill="url(#r)"
|
||||
filter="url(#k)"
|
||||
/>
|
||||
<path
|
||||
d="M219.6997 299.0227v59.9959c5.506.6402 11.2361 1.0289 17.2472 1.0289 6.0259 0 11.8556-.3073 17.5204-.8723v-59.7481c-6.3482 1.0777-12.3272 1.461-17.4776 1.461-5.9318 0-11.7005-.6858-17.29-1.8654Z"
|
||||
opacity=".5"
|
||||
fill="url(#s)"
|
||||
filter="url(#k)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<IconsBrandsGitHub v-if="provider === 'github'" />
|
||||
<IconsBrandsGoogle v-else-if="provider === 'google'" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
provider: OAUTH_PROVIDER;
|
||||
}>();
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<ClipboardDocumentIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ClipboardDocumentIcon from '@heroicons/vue/24/outline/esm/ClipboardDocumentIcon';
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<MagnifyingGlassIcon />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MagnifyingGlassIcon from '@heroicons/vue/24/outline/esm/MagnifyingGlassIcon';
|
||||
</script>
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div
|
||||
class="container mx-auto max-w-3xl overflow-hidden rounded-lg bg-white px-3 text-gray-700 shadow-md md:px-0 dark:bg-neutral-700 dark:text-neutral-200"
|
||||
>
|
||||
<slot />
|
||||
<div class="container mx-auto max-w-3xl">
|
||||
<div
|
||||
class="mx-3 overflow-hidden rounded-lg bg-white text-gray-700 shadow-md dark:bg-neutral-700 dark:text-neutral-200"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-shrink-0 space-x-1 md:block">
|
||||
<div class="flex flex-shrink-0 flex-col items-center gap-2 sm:flex-row">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-auto flex-grow flex-row items-center border-b-2 border-gray-100 p-3 px-5 dark:border-neutral-600"
|
||||
class="flex flex-col items-center gap-2 border-b-2 border-gray-100 p-3 px-5 sm:flex-row dark:border-neutral-600"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<h2 class="flex-1 text-2xl font-medium">
|
||||
{{ text }}
|
||||
<h2 class="flex-1 break-all text-2xl font-medium">
|
||||
<slot />
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { text } = defineProps<{
|
||||
text: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
href="https://github.com/wg-easy/wg-easy"
|
||||
>WireGuard Easy</a
|
||||
>
|
||||
({{ globalStore.information?.currentRelease }}) © 2021-2025 by
|
||||
({{ globalStore.information?.currentRelease }}) © 2021-2026 by
|
||||
<a
|
||||
class="hover:underline"
|
||||
target="_blank"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user