Compare commits
130 Commits
release-v1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aed10ab0bd | ||
|
|
478e7207b2 | ||
|
|
c8dc710435 | ||
|
|
19d9e3b7d7 | ||
|
|
c4efb1d03a | ||
|
|
529b9eeb88 | ||
|
|
69ee741d7e | ||
|
|
f2dc38e91b | ||
|
|
64e9484331 | ||
|
|
c777fa30b3 | ||
|
|
734d91fd98 | ||
|
|
84ed7b299f | ||
|
|
1cfe6404b2 | ||
|
|
2a32c1b9c0 | ||
|
|
3ef258a28a | ||
|
|
ff783fd4d1 | ||
|
|
65aa067100 | ||
|
|
48e6949a4d | ||
|
|
9ebf2c1d33 | ||
|
|
d0f85316a6 | ||
|
|
ff9fd553c5 | ||
|
|
e92ee0464e | ||
|
|
9df049d3f4 | ||
|
|
32b73b850a | ||
|
|
1c7f64ebd5 | ||
|
|
589ec1fe9a | ||
|
|
6e0d758e36 | ||
|
|
940edb2b0c | ||
|
|
d51f12a82f | ||
|
|
4a3747fa12 | ||
|
|
499fb096b6 | ||
|
|
c5c3a65bbf | ||
|
|
c133446f9c | ||
|
|
e8b3e54228 | ||
|
|
a9a51337da | ||
|
|
bbee7e04ed | ||
|
|
198b240755 | ||
|
|
86bdbe4c3d | ||
|
|
4890bb28e5 | ||
|
|
c3dbd3a815 | ||
|
|
fc480df910 | ||
|
|
b3bd2502af | ||
|
|
eb5ad91022 | ||
|
|
f2955a1278 | ||
|
|
1b76c066e0 | ||
|
|
5b68cc7311 | ||
|
|
0597470f4c | ||
|
|
159a51cff4 | ||
|
|
9fc6ebafb3 | ||
|
|
9a029eeb23 | ||
|
|
e5fb6ff3a6 | ||
|
|
fcb5049dab | ||
|
|
93db67bab6 | ||
|
|
842475f799 | ||
|
|
f4d3608da7 | ||
|
|
84ee8c35f0 | ||
|
|
ded148a0f5 | ||
|
|
9b29d72991 | ||
|
|
c6dce0f6fb | ||
|
|
ddc7394e1b | ||
|
|
6c752941f3 | ||
|
|
eda8040a03 | ||
|
|
bf77386ec6 | ||
|
|
6f2811c637 | ||
|
|
e75719b803 | ||
|
|
3fc0e02798 | ||
|
|
5aed67838c | ||
|
|
99e1e15b03 | ||
|
|
6dc89257ff | ||
|
|
a32655992f | ||
|
|
2bd1dffe7e | ||
|
|
3c959ba3bb | ||
|
|
0b204a6f21 | ||
|
|
efe5cccb02 | ||
|
|
31c59c1b26 | ||
|
|
6e891ad9fb | ||
|
|
e2fe3b8b32 | ||
|
|
c0fbc3f647 | ||
|
|
f3324cbb28 | ||
|
|
33b4c3859d | ||
|
|
067b7bcf84 | ||
|
|
942f35916c | ||
|
|
52d83dbf35 | ||
|
|
4dc56a0718 | ||
|
|
66c64af7cf | ||
|
|
f4b3d4fb6b | ||
|
|
3427d677f6 | ||
|
|
4ba7dc244c | ||
|
|
3f6b6f3c9b | ||
|
|
11872de321 | ||
|
|
4758c0dddc | ||
|
|
96030c08f4 | ||
|
|
ba989aa829 | ||
|
|
78fab2b5d8 | ||
|
|
dd04dd285d | ||
|
|
7be9884aec | ||
|
|
41be774761 | ||
|
|
ed93c6c8ed | ||
|
|
bbffc22ae3 | ||
|
|
800ec155c1 | ||
|
|
75df17476f | ||
|
|
86f968499a | ||
|
|
968d2b90a0 | ||
|
|
352a022f30 | ||
|
|
8145809e22 | ||
|
|
40af030266 | ||
|
|
bb2e8d2751 | ||
|
|
ca7ee32052 | ||
|
|
7c521e8733 | ||
|
|
f5885e335c | ||
|
|
36b9ff60c8 | ||
|
|
cd3d4efebf | ||
|
|
81633de07b | ||
|
|
0a33b1f7df | ||
|
|
2ea37dd7ba | ||
|
|
2fe5269bbe | ||
|
|
8591b35d4e | ||
|
|
7fbc2702a6 | ||
|
|
4ead4c2cc9 | ||
|
|
3e6ded18a5 | ||
|
|
4e79d0ee03 | ||
|
|
6d59e16161 | ||
|
|
0386a0da6b | ||
|
|
0bf266d5cb | ||
|
|
0ca39b4f34 | ||
|
|
a268422e9d | ||
|
|
8921d6c661 | ||
|
|
b18f919419 | ||
|
|
ea99f56484 | ||
|
|
aea653a071 |
@@ -1 +1,3 @@
|
|||||||
/src/node_modules
|
/src/node_modules
|
||||||
|
/src/.nuxt
|
||||||
|
/src/.output
|
||||||
|
|||||||
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -1,4 +1,5 @@
|
|||||||
# Copyright (c) Emile Nijssen (WeeJeWel)
|
# Copyright (c) Emile Nijssen (WeeJeWel)
|
||||||
# Founder and Codeowner of WireGuard Easy (wg-easy)
|
# Founder and Codeowner of WireGuard Easy (wg-easy)
|
||||||
# Maintained by Philip Heiduck (pheiduck)
|
# Maintained by Bernd Storath (kaaax0815)
|
||||||
* @pheiduck
|
* @WeeJeWel
|
||||||
|
* @kaaax0815
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: weejewel
|
github: [weejewel, kaaax0815]
|
||||||
|
|||||||
37
.github/ISSUE_TEMPLATE/01-bug-report.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/01-bug-report.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: 🐛 Bug Report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
title: "[Bug]: "
|
||||||
|
type: Bug
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
**Thanks :heart: for taking the time to fill out this bug report!**
|
||||||
|
We kindly ask that you search to see if an issue [already exists](https://github.com/wg-easy/wg-easy/issues?q=is%3Aissue+sort%3Acreated-desc+) for the bug you encountered.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
placeholder: Tell us what you see!
|
||||||
|
value: "A bug happened!"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: what-should-happen
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
placeholder: Tell us what you expected!
|
||||||
|
value: "Work just fine!"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output
|
||||||
|
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
47
.github/ISSUE_TEMPLATE/02-feature-request.yml
vendored
Normal file
47
.github/ISSUE_TEMPLATE/02-feature-request.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
name: 🛠️ Feature Request
|
||||||
|
description: Suggest an idea to help us improve
|
||||||
|
title: "[Feat]: "
|
||||||
|
type: Feature
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
**Thanks :heart: for taking the time to fill out this feature request report!**
|
||||||
|
We kindly ask that you search to see if an issue [already exists](https://github.com/wg-easy/wg-easy/issues?q=is%3Aissue+sort%3Acreated-desc+) for your feature.
|
||||||
|
|
||||||
|
We are also happy to accept contributions from our users. For more details see [here](https://github.com/wg-easy/wg-easy/blob/master/contributing.md).
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: |
|
||||||
|
A clear and concise description of the feature you're interested in.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Suggested Solution
|
||||||
|
description: |
|
||||||
|
Describe the solution you'd like. A clear and concise description of what you want to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Alternatives
|
||||||
|
description: |
|
||||||
|
Describe alternatives you've considered.
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: |
|
||||||
|
Add any other context about the problem here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. macOS 12.1]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS 8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
contact_links:
|
||||||
|
- name: Get Help
|
||||||
|
url: https://github.com/wg-easy/wg-easy/discussions/new?category=q-a
|
||||||
|
about: If you can't get something to work the way you expect, open a question in the discussions.
|
||||||
|
blank_issues_enabled: false
|
||||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -5,3 +5,13 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
rebase-strategy: "auto"
|
rebase-strategy: "auto"
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
rebase-strategy: "auto"
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/src/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
rebase-strategy: "auto"
|
||||||
|
|||||||
34
.github/workflows/codeql.yml
vendored
34
.github/workflows/codeql.yml
vendored
@@ -1,10 +1,12 @@
|
|||||||
name: "CodeQL"
|
name: CodeQL
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "master" ]
|
branches:
|
||||||
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "master" ]
|
branches:
|
||||||
|
- master
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "15 0 * * *"
|
- cron: "15 0 * * *"
|
||||||
|
|
||||||
@@ -21,21 +23,21 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
language: [ 'javascript-typescript' ]
|
language: ["javascript-typescript"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
163
.github/workflows/deploy-development.yml
vendored
163
.github/workflows/deploy-development.yml
vendored
@@ -1,37 +1,154 @@
|
|||||||
name: Build & Publish Development
|
name: Development
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
docker-build:
|
||||||
name: Build & Deploy
|
name: Build Docker
|
||||||
|
runs-on: ${{ matrix.arch.os }}
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch:
|
||||||
|
- platform: linux/amd64
|
||||||
|
os: ubuntu-latest
|
||||||
|
- platform: linux/arm64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.arch.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ matrix.arch.platform }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
tags: ghcr.io/wg-easy/wg-easy
|
||||||
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
docker-merge:
|
||||||
|
name: Merge & Deploy Docker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository_owner == 'wg-easy'
|
if: github.repository_owner == 'wg-easy'
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
needs: docker-build
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Download digests
|
||||||
with:
|
uses: actions/download-artifact@v4
|
||||||
ref: production
|
with:
|
||||||
|
path: ${{ runner.temp }}/digests
|
||||||
|
pattern: digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Docker meta
|
||||||
uses: docker/login-action@v3
|
id: meta
|
||||||
with:
|
uses: docker/metadata-action@v5
|
||||||
registry: ghcr.io
|
with:
|
||||||
username: ${{ github.actor }}
|
images: |
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=raw,value=development
|
||||||
|
|
||||||
- name: Build & Publish Docker Image
|
- name: Create manifest list and push
|
||||||
uses: docker/build-push-action@v6
|
working-directory: ${{ runner.temp }}/digests
|
||||||
with:
|
run: |
|
||||||
push: true
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
|
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
|
||||||
tags: ghcr.io/wg-easy/wg-easy:development
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
|
||||||
|
|
||||||
|
docs:
|
||||||
|
name: Build & Deploy Docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
needs: docker-merge
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.11.9
|
||||||
|
cache: "pip"
|
||||||
|
cache-dependency-path: docs/requirements.txt
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r docs/requirements.txt
|
||||||
|
|
||||||
|
- name: Setup Git User
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'github-actions[bot]'
|
||||||
|
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
|
||||||
|
- name: Build Docs Website
|
||||||
|
run: |
|
||||||
|
cd docs
|
||||||
|
git fetch origin gh-pages --depth=1 || true
|
||||||
|
|
||||||
|
mike deploy --push --update-aliases development
|
||||||
|
|||||||
165
.github/workflows/deploy-edge.yml
vendored
Normal file
165
.github/workflows/deploy-edge.yml
vendored
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
name: Edge
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-build:
|
||||||
|
name: Build Docker
|
||||||
|
runs-on: ${{ matrix.arch.os }}
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch:
|
||||||
|
- platform: linux/amd64
|
||||||
|
os: ubuntu-latest
|
||||||
|
- platform: linux/arm64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.arch.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ matrix.arch.platform }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
tags: ghcr.io/wg-easy/wg-easy
|
||||||
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
docker-merge:
|
||||||
|
name: Merge & Deploy Docker
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
needs: docker-build
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.temp }}/digests
|
||||||
|
pattern: digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=raw,value=edge
|
||||||
|
|
||||||
|
- name: Create manifest list and push
|
||||||
|
working-directory: ${{ runner.temp }}/digests
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
|
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
|
||||||
|
|
||||||
|
docs:
|
||||||
|
name: Build & Deploy Docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
needs: docker-merge
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.11.9
|
||||||
|
cache: "pip"
|
||||||
|
cache-dependency-path: docs/requirements.txt
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r docs/requirements.txt
|
||||||
|
|
||||||
|
- name: Setup Git User
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'github-actions[bot]'
|
||||||
|
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
|
||||||
|
- name: Build Docs Website
|
||||||
|
run: |
|
||||||
|
cd docs
|
||||||
|
git fetch origin gh-pages --depth=1 || true
|
||||||
|
|
||||||
|
mike deploy --push --update-aliases edge
|
||||||
39
.github/workflows/deploy-nightly.yml
vendored
39
.github/workflows/deploy-nightly.yml
vendored
@@ -1,39 +0,0 @@
|
|||||||
name: Build & Publish Nightly
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Build & Deploy
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository_owner == 'wg-easy'
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: production
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build & Publish Docker Image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
|
|
||||||
tags: ghcr.io/wg-easy/wg-easy:nightly
|
|
||||||
67
.github/workflows/deploy-pr.yml
vendored
67
.github/workflows/deploy-pr.yml
vendored
@@ -1,38 +1,53 @@
|
|||||||
name: Build Pull Request
|
name: Pull Request
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
docker:
|
||||||
name: Build & Deploy
|
name: Build Docker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.arch.os }}
|
||||||
if: github.repository_owner == 'wg-easy'
|
if: github.repository_owner == 'wg-easy'
|
||||||
permissions:
|
strategy:
|
||||||
packages: write
|
fail-fast: false
|
||||||
contents: read
|
matrix:
|
||||||
|
arch:
|
||||||
|
- platform: linux/amd64
|
||||||
|
os: ubuntu-latest
|
||||||
|
- platform: linux/arm64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
ref: production
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Prepare
|
||||||
uses: docker/setup-qemu-action@v3
|
run: |
|
||||||
|
platform=${{ matrix.arch.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up QEMU
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Set up Docker Buildx
|
||||||
uses: docker/login-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build Docker Image
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
push: false
|
registry: ghcr.io
|
||||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
|
username: ${{ github.actor }}
|
||||||
tags: ghcr.io/wg-easy/wg-easy:pr
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Docker Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
platforms: ${{ matrix.arch.platform }}
|
||||||
|
tags: ghcr.io/wg-easy/wg-easy:pr
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
|||||||
194
.github/workflows/deploy.yml
vendored
194
.github/workflows/deploy.yml
vendored
@@ -1,43 +1,179 @@
|
|||||||
name: Build & Publish Latest
|
name: Production
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
tags:
|
||||||
- production
|
- "v*"
|
||||||
|
|
||||||
|
# This workflow does not support fixing old versions
|
||||||
|
# as this will break the latest and major tags
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
docker-build:
|
||||||
name: Build & Deploy
|
name: Build Docker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.arch.os }}
|
||||||
if: github.repository_owner == 'wg-easy'
|
if: |
|
||||||
|
github.repository_owner == 'wg-easy' &&
|
||||||
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
arch:
|
||||||
|
- platform: linux/amd64
|
||||||
|
os: ubuntu-latest
|
||||||
|
- platform: linux/arm64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
ref: production
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Prepare
|
||||||
uses: docker/setup-qemu-action@v3
|
run: |
|
||||||
|
platform=${{ matrix.arch.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Docker meta
|
||||||
uses: docker/setup-buildx-action@v3
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set up QEMU
|
||||||
run: echo RELEASE=$(cat ./src/package.json | jq -r .release | jq -r .version) >> $GITHUB_ENV
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Build & Publish Docker Image
|
- name: Set up Docker Buildx
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
|
||||||
push: true
|
- name: Build and push by digest
|
||||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
|
id: build
|
||||||
tags: ghcr.io/wg-easy/wg-easy:latest, ghcr.io/wg-easy/wg-easy:${{ env.RELEASE }}
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ matrix.arch.platform }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
tags: ghcr.io/wg-easy/wg-easy
|
||||||
|
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: ${{ runner.temp }}/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
docker-merge:
|
||||||
|
name: Merge & Deploy Docker
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.repository_owner == 'wg-easy' &&
|
||||||
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
needs: docker-build
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.temp }}/digests
|
||||||
|
pattern: digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/wg-easy/wg-easy
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
|
||||||
|
- name: Create manifest list and push
|
||||||
|
working-directory: ${{ runner.temp }}/digests
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
|
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
|
||||||
|
|
||||||
|
docs:
|
||||||
|
name: Build & Deploy Docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.repository_owner == 'wg-easy' &&
|
||||||
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
needs: docker-merge
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.11.9
|
||||||
|
cache: "pip"
|
||||||
|
cache-dependency-path: docs/requirements.txt
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r docs/requirements.txt
|
||||||
|
|
||||||
|
- name: Setup Git User
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'github-actions[bot]'
|
||||||
|
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
|
||||||
|
- name: Build Docs Website
|
||||||
|
run: |
|
||||||
|
cd docs
|
||||||
|
git fetch origin gh-pages --depth=1 || true
|
||||||
|
|
||||||
|
# Extract version numbers
|
||||||
|
DOCS_VERSION=${GITHUB_REF#refs/tags/} # e.g. v1.2.3 or v1.2.3-beta
|
||||||
|
MINOR_VERSION=$(echo $DOCS_VERSION | cut -d. -f1,2) # e.g. v1.2
|
||||||
|
|
||||||
|
# Check if it's a stable release (only numbers, no '-')
|
||||||
|
if [[ "$DOCS_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "Stable release detected: $DOCS_VERSION"
|
||||||
|
mike deploy --push --update-aliases $MINOR_VERSION latest
|
||||||
|
else
|
||||||
|
echo "Pre-release detected: $DOCS_VERSION"
|
||||||
|
mike deploy --push --update-aliases Pre-release
|
||||||
|
fi
|
||||||
|
|||||||
55
.github/workflows/lint.yml
vendored
55
.github/workflows/lint.yml
vendored
@@ -4,26 +4,65 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- production
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
docs:
|
||||||
name: Lint
|
name: Check Docs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository_owner == 'wg-easy'
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: "lts/*"
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: 'npm'
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: npm run lint
|
- name: Check docs formatting
|
||||||
|
run: |
|
||||||
|
pnpm install
|
||||||
|
pnpm format:check:docs
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: docs
|
||||||
|
if: github.repository_owner == 'wg-easy'
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 3
|
||||||
|
matrix:
|
||||||
|
command: ["lint", "typecheck", "format:check"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "lts/*"
|
||||||
|
check-latest: true
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: pnpm ${{ matrix.command }}
|
||||||
run: |
|
run: |
|
||||||
cd src
|
cd src
|
||||||
npm ci
|
pnpm install
|
||||||
npm run lint
|
pnpm ${{ matrix.command }}
|
||||||
|
|||||||
40
.github/workflows/npm-update-bot.yml
vendored
40
.github/workflows/npm-update-bot.yml
vendored
@@ -1,40 +0,0 @@
|
|||||||
name: NPM Update Bot 🤖
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * 1"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
npmupbot:
|
|
||||||
name: NPM Update Bot 🤖
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository_owner == 'wg-easy'
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: wg-easy/wg-easy
|
|
||||||
ref: master
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
check-latest: true
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: Bot 🤖 "Updating NPM Packages..."
|
|
||||||
run: |
|
|
||||||
npm install -g --silent npm-check-updates
|
|
||||||
ncu -u
|
|
||||||
npm update
|
|
||||||
cd src
|
|
||||||
ncu -u
|
|
||||||
npm update
|
|
||||||
npm run buildcss
|
|
||||||
git config --global user.name 'NPM Update Bot'
|
|
||||||
git config --global user.email 'npmupbot@users.noreply.github.com'
|
|
||||||
git add .
|
|
||||||
git commit -am "npm: package updates" || true
|
|
||||||
git push
|
|
||||||
7
.github/workflows/stale.yml
vendored
7
.github/workflows/stale.yml
vendored
@@ -8,11 +8,10 @@ name: Mark stale issues and pull requests
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '*/5 * * * *'
|
- cron: "*/5 * * * *"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository_owner == 'wg-easy'
|
if: github.repository_owner == 'wg-easy'
|
||||||
permissions:
|
permissions:
|
||||||
@@ -20,8 +19,8 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
days-before-issue-stale: 30
|
days-before-issue-stale: 30
|
||||||
days-before-issue-close: 14
|
days-before-issue-close: 14
|
||||||
stale-issue-label: "stale"
|
stale-issue-label: "stale"
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,6 +1,3 @@
|
|||||||
/config
|
|
||||||
/wg0.conf
|
|
||||||
/wg0.json
|
|
||||||
/src/node_modules
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.swp
|
*.swp
|
||||||
|
node_modules
|
||||||
15
.vscode/extensions.json
vendored
Normal file
15
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"aaron-bond.better-comments",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"antfu.goto-alias",
|
||||||
|
"visualstudioexptteam.vscodeintellicode",
|
||||||
|
"Nuxtr.nuxtr-vscode",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"yoavbls.pretty-ts-errors",
|
||||||
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"vue.volar",
|
||||||
|
"lokalise.i18n-ally",
|
||||||
|
"DavidAnson.vscode-markdownlint"
|
||||||
|
]
|
||||||
|
}
|
||||||
33
.vscode/settings.json
vendored
Normal file
33
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.useTabStops": false,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"nuxtr.vueFiles.style.addStyleTag": false,
|
||||||
|
"nuxtr.piniaFiles.defaultTemplate": "setup",
|
||||||
|
"nuxtr.monorepoMode.DirectoryName": "src",
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "always"
|
||||||
|
},
|
||||||
|
"[vue]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[markdown]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"editor.useTabStops": false
|
||||||
|
},
|
||||||
|
"typescript.tsdk": "./src/node_modules/typescript/lib",
|
||||||
|
"i18n-ally.enabledFrameworks": ["vue"],
|
||||||
|
"i18n-ally.localesPaths": ["src/i18n/locales"],
|
||||||
|
"i18n-ally.sortKeys": false,
|
||||||
|
"i18n-ally.keepFulfilled": false,
|
||||||
|
"i18n-ally.keystyle": "nested",
|
||||||
|
"editor.gotoLocation.multipleDefinitions": "goto"
|
||||||
|
}
|
||||||
42
CHANGELOG.md
Normal file
42
CHANGELOG.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
As the whole setup has changed, we recommend to start from scratch. And import your existing configs.
|
||||||
|
|
||||||
|
## Major Changes
|
||||||
|
|
||||||
|
- Almost all Environment variables removed
|
||||||
|
- New and Improved UI
|
||||||
|
- API Basic Authentication
|
||||||
|
- Added Docs
|
||||||
|
- Incrementing Version -> Semantic Versioning
|
||||||
|
- CIDR Support
|
||||||
|
- IPv6 Support
|
||||||
|
- Changed API Structure
|
||||||
|
- SQLite Database
|
||||||
|
- Deprecated Dockerless Installations
|
||||||
|
- Added Docker Volume Mount (`/lib/modules`)
|
||||||
|
- Removed ARMv6 and ARMv7 support
|
||||||
|
- Connections over HTTP require setting the `INSECURE` env var
|
||||||
|
- Changed license from CC BY-NC-SA 4.0 to AGPL-3.0-only
|
||||||
|
- Added 2FA using TOTP
|
||||||
|
- Improved mobile support
|
||||||
|
- CLI
|
||||||
|
- Replaced `nightly` with `edge`
|
||||||
|
|
||||||
|
## [14.0.0] - 2024-09-04
|
||||||
|
|
||||||
|
### Major changes
|
||||||
|
|
||||||
|
- `PASSWORD` has been replaced by `PASSWORD_HASH`
|
||||||
66
Dockerfile
66
Dockerfile
@@ -1,49 +1,61 @@
|
|||||||
# As a workaround we have to build on nodejs 18
|
FROM docker.io/library/node:lts-alpine AS build
|
||||||
# nodejs 20 hangs on build with armv6/armv7
|
WORKDIR /app
|
||||||
FROM docker.io/library/node:18-alpine AS build_node_modules
|
|
||||||
|
|
||||||
# Update npm to latest
|
# update corepack
|
||||||
RUN npm install -g npm@latest
|
RUN npm install --global corepack@latest
|
||||||
|
# Install pnpm
|
||||||
|
RUN corepack enable pnpm
|
||||||
|
|
||||||
# Copy Web UI
|
# Copy Web UI
|
||||||
COPY src /app
|
COPY src/package.json src/pnpm-lock.yaml ./
|
||||||
WORKDIR /app
|
RUN pnpm install
|
||||||
RUN npm ci --omit=dev &&\
|
|
||||||
mv node_modules /node_modules
|
# Build UI
|
||||||
|
COPY src ./
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
# Copy build result to a new image.
|
# Copy build result to a new image.
|
||||||
# This saves a lot of disk space.
|
# This saves a lot of disk space.
|
||||||
FROM docker.io/library/node:20-alpine
|
FROM docker.io/library/node:lts-alpine
|
||||||
HEALTHCHECK CMD /usr/bin/timeout 5s /bin/sh -c "/usr/bin/wg show | /bin/grep -q interface || exit 1" --interval=1m --timeout=5s --retries=3
|
WORKDIR /app
|
||||||
COPY --from=build_node_modules /app /app
|
|
||||||
|
|
||||||
# Move node_modules one directory up, so during development
|
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"
|
||||||
# we don't have to mount it in a volume.
|
|
||||||
# This results in much faster reloading!
|
|
||||||
#
|
|
||||||
# Also, some node_modules might be native, and
|
|
||||||
# the architecture & OS of your development machine might differ
|
|
||||||
# than what runs inside of docker.
|
|
||||||
COPY --from=build_node_modules /node_modules /node_modules
|
|
||||||
|
|
||||||
# Copy the needed wg-password scripts
|
# Copy build
|
||||||
COPY --from=build_node_modules /app/wgpw.sh /bin/wgpw
|
COPY --from=build /app/.output /app
|
||||||
RUN chmod +x /bin/wgpw
|
# 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
|
||||||
|
# cli
|
||||||
|
COPY --from=build /app/cli/cli.sh /usr/local/bin/cli
|
||||||
|
RUN chmod +x /usr/local/bin/cli
|
||||||
|
|
||||||
# Install Linux packages
|
# Install Linux packages
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
dpkg \
|
dpkg \
|
||||||
dumb-init \
|
dumb-init \
|
||||||
iptables \
|
iptables \
|
||||||
|
ip6tables \
|
||||||
|
nftables \
|
||||||
|
kmod \
|
||||||
iptables-legacy \
|
iptables-legacy \
|
||||||
wireguard-tools
|
wireguard-tools
|
||||||
|
|
||||||
# Use iptables-legacy
|
# Use iptables-legacy
|
||||||
RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save
|
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
|
# Set Environment
|
||||||
ENV DEBUG=Server,WireGuard
|
ENV DEBUG=Server,WireGuard,Database,CMD
|
||||||
|
ENV PORT=51821
|
||||||
|
ENV HOST=0.0.0.0
|
||||||
|
ENV INSECURE=false
|
||||||
|
ENV INIT_ENABLED=false
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source=https://github.com/wg-easy/wg-easy
|
||||||
|
|
||||||
# Run Web UI
|
# Run Web UI
|
||||||
WORKDIR /app
|
CMD ["/usr/bin/dumb-init", "node", "server/index.mjs"]
|
||||||
CMD ["/usr/bin/dumb-init", "node", "server.js"]
|
|
||||||
|
|||||||
39
Dockerfile.dev
Normal file
39
Dockerfile.dev
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
FROM docker.io/library/node:lts-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# update corepack
|
||||||
|
RUN npm install --global corepack@latest
|
||||||
|
# Install pnpm
|
||||||
|
RUN corepack enable pnpm
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
# Install Linux packages
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
dpkg \
|
||||||
|
dumb-init \
|
||||||
|
iptables \
|
||||||
|
ip6tables \
|
||||||
|
kmod \
|
||||||
|
iptables-legacy \
|
||||||
|
wireguard-tools
|
||||||
|
|
||||||
|
# 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 PORT=51821
|
||||||
|
ENV HOST=0.0.0.0
|
||||||
|
ENV INSECURE=true
|
||||||
|
ENV INIT_ENABLED=false
|
||||||
|
|
||||||
|
# Install Dependencies
|
||||||
|
COPY src/package.json src/pnpm-lock.yaml ./
|
||||||
|
RUN pnpm install
|
||||||
|
|
||||||
|
# Copy Project
|
||||||
|
COPY src ./
|
||||||
|
|
||||||
|
ENTRYPOINT [ "pnpm", "run" ]
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# wg-password
|
|
||||||
|
|
||||||
`wg-password` (wgpw) is a script that generates bcrypt password hashes for use with `wg-easy`, enhancing security by requiring passwords.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Generate bcrypt password hashes.
|
|
||||||
- Easily integrate with `wg-easy` to enforce password requirements.
|
|
||||||
|
|
||||||
## Usage with Docker
|
|
||||||
|
|
||||||
To generate a bcrypt password hash using docker, run the following command :
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker run ghcr.io/wg-easy/wg-easy wgpw YOUR_PASSWORD
|
|
||||||
PASSWORD_HASH='$2b$12$coPqCsPtcFO.Ab99xylBNOW4.Iu7OOA2/ZIboHN6/oyxca3MWo7fW' // literally YOUR_PASSWORD
|
|
||||||
```
|
|
||||||
|
|
||||||
*Important* : make sure to enclose your password in single quotes when you run `docker run` command :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ echo $2b$12$coPqCsPtcF
|
|
||||||
b2
|
|
||||||
$ echo "$2b$12$coPqCsPtcF"
|
|
||||||
b2
|
|
||||||
$ echo '$2b$12$coPqCsPtcF'
|
|
||||||
$2b$12$coPqCsPtcF
|
|
||||||
```
|
|
||||||
205
README.md
205
README.md
@@ -1,56 +1,75 @@
|
|||||||
# WireGuard Easy
|
# 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/actions/workflows/lint.yml)
|
||||||

|
[](https://github.com/wg-easy/wg-easy/stargazers)
|
||||||
[](https://github.com/sponsors/WeeJeWel)
|
[](LICENSE)
|
||||||

|
[](https://github.com/wg-easy/wg-easy/releases/latest)
|
||||||
|
[](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy)
|
||||||
|
|
||||||
|
<!-- TODO: remove after release -->
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> You are viewing the README of the pre-release of v15.
|
||||||
|
> If you want to setup wg-easy right now. Read the README in the production branch here: [README](https://github.com/wg-easy/wg-easy/tree/production) or here for the last nightly: [README](https://github.com/wg-easy/wg-easy/tree/c6dce0f6fb2e28e7e40ddac1498bd67e9bb17cba)
|
||||||
|
|
||||||
You have found the easiest way to install & manage WireGuard on any Linux host!
|
You have found the easiest way to install & manage WireGuard on any Linux host!
|
||||||
|
|
||||||
|
<!-- TOOD: update screenshot -->
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./assets/screenshot.png" width="802" />
|
<img src="./assets/screenshot.png" width="802" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* All-in-one: WireGuard + Web UI.
|
|
||||||
* Easy installation, simple to use.
|
|
||||||
* List, create, edit, delete, enable & disable clients.
|
|
||||||
* Show a client's QR code.
|
|
||||||
* Download a client's configuration file.
|
|
||||||
* Statistics for which clients are connected.
|
|
||||||
* Tx/Rx charts for each connected client.
|
|
||||||
* Gravatar support.
|
|
||||||
* Automatic Light / Dark Mode
|
|
||||||
* Multilanguage Support
|
|
||||||
* UI_TRAFFIC_STATS (default off)
|
|
||||||
|
|
||||||
## Requirements
|
- All-in-one: WireGuard + Web UI.
|
||||||
|
- Easy installation, simple to use.
|
||||||
|
- List, create, edit, delete, enable & disable clients.
|
||||||
|
- Show a client's QR code.
|
||||||
|
- Download a client's configuration file.
|
||||||
|
- Statistics for which clients are connected.
|
||||||
|
- Tx/Rx charts for each connected client.
|
||||||
|
- Gravatar support.
|
||||||
|
- Automatic Light / Dark Mode
|
||||||
|
- Multilanguage Support
|
||||||
|
- One Time Links
|
||||||
|
- Client Expiration
|
||||||
|
- Prometheus metrics support
|
||||||
|
- IPv6 support
|
||||||
|
- CIDR support
|
||||||
|
- 2FA support
|
||||||
|
|
||||||
* A host with a kernel that supports WireGuard (all modern kernels).
|
> [!NOTE]
|
||||||
* A host with Docker installed.
|
> 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)
|
||||||
|
|
||||||
## Versions
|
<!-- TODO: remove after release -->
|
||||||
|
|
||||||
We provide more then 1 docker image to get, this will help you decide which one is best for you. <br>
|
> [!WARNING]
|
||||||
For **stable** versions instead of nightly or development please read **README** from the **production** branch!
|
> As the Docs are still in Pre-release, you can access them here [https://wg-easy.github.io/wg-easy/Pre-release](https://wg-easy.github.io/wg-easy/Pre-release)
|
||||||
|
|
||||||
| tag | Branch | Example | Description |
|
- [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/)
|
||||||
| - | - | - | - |
|
- [Basic Installation](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/)
|
||||||
| `latest` | production | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | stable as possbile get bug fixes quickly when needed, deployed against `production`. |
|
- [Caddy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/caddy/)
|
||||||
| `13` | production | `ghcr.io/wg-easy/wg-easy:13` | same as latest, stick to a version tag. |
|
- [Traefik](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/traefik/)
|
||||||
| `nightly` | master | `ghcr.io/wg-easy/wg-easy:nightly` | mostly unstable gets frequent package and code updates, deployed against `master`. |
|
- [Podman](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/podman-nft/)
|
||||||
| `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs before landing into `master`. |
|
- [AdGuard Home](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/adguard/)
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If you want to migrate from the old version to the new version, you can find the migration guide here: [Migration Guide](https://wg-easy.github.io/wg-easy/latest/advanced/migrate/)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
This is a quick start guide to get you up and running with WireGuard Easy.
|
||||||
|
|
||||||
|
For a more detailed installation guide, please refer to the [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/) page.
|
||||||
|
|
||||||
### 1. Install Docker
|
### 1. Install Docker
|
||||||
|
|
||||||
If you haven't installed Docker yet, install it by running:
|
If you haven't installed Docker yet, install it by running as root:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
curl -sSL https://get.docker.com | sh
|
curl -sSL https://get.docker.com | sh
|
||||||
sudo usermod -aG docker $(whoami)
|
|
||||||
exit
|
exit
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -58,94 +77,60 @@ And log in again.
|
|||||||
|
|
||||||
### 2. Run WireGuard Easy
|
### 2. Run WireGuard Easy
|
||||||
|
|
||||||
To automatically install & run wg-easy, simply run:
|
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`.
|
||||||
docker run -d \
|
|
||||||
--name=wg-easy \
|
Now setup a reverse proxy to be able to access the Web UI securely from the internet.
|
||||||
-e LANG=de \
|
|
||||||
-e WG_HOST=<🚨YOUR_SERVER_IP> \
|
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
|
||||||
-e PASSWORD_HASH=<🚨YOUR_ADMIN_PASSWORD_HASH> \
|
|
||||||
-e PORT=51821 \
|
## Donate
|
||||||
-e WG_PORT=51820 \
|
|
||||||
-v ~/.wg-easy:/etc/wireguard \
|
Are you enjoying this project? Consider donating.
|
||||||
-p 51820:51820/udp \
|
|
||||||
-p 51821:51821/tcp \
|
Founder: [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻
|
||||||
--cap-add=NET_ADMIN \
|
|
||||||
--cap-add=SYS_MODULE \
|
Maintainer: [Buy kaaax0815 a coffee!](https://github.com/sponsors/kaaax0815) ☕
|
||||||
--sysctl="net.ipv4.conf.all.src_valid_mark=1" \
|
|
||||||
--sysctl="net.ipv4.ip_forward=1" \
|
## Development
|
||||||
--restart unless-stopped \
|
|
||||||
ghcr.io/wg-easy/wg-easy
|
### Prerequisites
|
||||||
|
|
||||||
|
- Docker
|
||||||
|
- Node LTS & corepack enabled
|
||||||
|
- Visual Studio Code
|
||||||
|
|
||||||
|
### Dev Server
|
||||||
|
|
||||||
|
This starts the development server with docker
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
> 💡 Replace `YOUR_SERVER_IP` with your WAN IP, or a Dynamic DNS hostname.
|
### Update Auto Imports
|
||||||
>
|
|
||||||
> 💡 Replace `YOUR_ADMIN_PASSWORD_HASH` with a bcrypt password hash to log in on the Web UI. See [How_to_generate_an_bcrypt_hash.md](./How_to_generate_an_bcrypt_hash.md) for know how generate the hash.
|
|
||||||
|
|
||||||
The Web UI will now be available on `http://0.0.0.0:51821`.
|
If you add something that should be auto-importable and VSCode complains, run:
|
||||||
|
|
||||||
> 💡 Your configuration files will be saved in `~/.wg-easy`
|
```shell
|
||||||
|
cd src
|
||||||
WireGuard Easy can be launched with Docker Compose as well - just download
|
pnpm install
|
||||||
[`docker-compose.yml`](docker-compose.yml), make necessary adjustments and
|
cd ..
|
||||||
execute `docker compose up --detach`.
|
|
||||||
|
|
||||||
### 3. Sponsor
|
|
||||||
|
|
||||||
Are you enjoying this project? [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻
|
|
||||||
|
|
||||||
## Options
|
|
||||||
|
|
||||||
These options can be configured by setting environment variables using `-e KEY="VALUE"` in the `docker run` command.
|
|
||||||
|
|
||||||
| Env | Default | Example | Description |
|
|
||||||
| - | - | - |------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
|
|
||||||
| `WEBUI_HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
|
|
||||||
| `PASSWORD_HASH` | - | `$2y$05$Ci...` | When set, requires a password when logging in to the Web UI. See [How to generate an bcrypt hash.md]("https://github.com/wg-easy/wg-easy/blob/master/How_to_generate_an_bcrypt_hash.md") for know how generate the hash. |
|
|
||||||
| `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. |
|
|
||||||
| `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. |
|
|
||||||
| `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will listen on that (othwise default) inside the Docker container. |
|
|
||||||
| `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistant Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy)
|
|
||||||
| `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. |
|
|
||||||
| `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. |
|
|
||||||
| `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. |
|
|
||||||
| `WG_DEFAULT_DNS` | `1.1.1.1` | `8.8.8.8, 8.8.4.4` | DNS server clients will use. If set to blank value, clients will not use any DNS. |
|
|
||||||
| `WG_ALLOWED_IPS` | `0.0.0.0/0, ::/0` | `192.168.15.0/24, 10.0.1.0/24` | Allowed IPs clients will use. |
|
|
||||||
| `WG_PRE_UP` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L19) for the default value. |
|
|
||||||
| `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. |
|
|
||||||
| `WG_PRE_DOWN` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L27) for the default value. |
|
|
||||||
| `WG_POST_DOWN` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L28) for the default value. |
|
|
||||||
| `LANG` | `en` | `de` | Web UI language (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi). |
|
|
||||||
| `UI_TRAFFIC_STATS` | `false` | `true` | Enable detailed RX / TX client stats in Web UI |
|
|
||||||
| `UI_CHART_TYPE` | `0` | `1` | UI_CHART_TYPE=0 # Charts disabled, UI_CHART_TYPE=1 # Line chart, UI_CHART_TYPE=2 # Area chart, UI_CHART_TYPE=3 # Bar chart |
|
|
||||||
|
|
||||||
> If you change `WG_PORT`, make sure to also change the exposed port.
|
|
||||||
|
|
||||||
## Updating
|
|
||||||
|
|
||||||
To update to the latest version, simply run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker stop wg-easy
|
|
||||||
docker rm wg-easy
|
|
||||||
docker pull ghcr.io/wg-easy/wg-easy
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And then run the `docker run -d \ ...` command above again.
|
### Test Cli
|
||||||
|
|
||||||
With Docker Compose WireGuard Easy can be updated with a single command:
|
This starts the cli with docker
|
||||||
`docker compose up --detach --pull always` (if an image tag is specified in the
|
|
||||||
Compose file and it is not `latest`, make sure that it is changed to the desired
|
|
||||||
one; by default it is omitted and
|
|
||||||
[defaults to `latest`](https://docs.docker.com/engine/reference/run/#image-references)). \
|
|
||||||
The WireGuared Easy container will be automatically recreated if a newer image
|
|
||||||
was pulled.
|
|
||||||
|
|
||||||
## Common Use Cases
|
```shell
|
||||||
|
pnpm cli:dev
|
||||||
|
```
|
||||||
|
|
||||||
* [Using WireGuard-Easy with Pi-Hole](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-Pi-Hole)
|
## License
|
||||||
* [Using WireGuard-Easy with nginx/SSL](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-nginx-SSL)
|
|
||||||
|
|
||||||
For less common or specific edge-case scenarios, please refer to the detailed information provided in the [Wiki](https://github.com/wg-easy/wg-easy/wiki).
|
This project is licensed under the AGPL-3.0-only License - see the [LICENSE](LICENSE) file for details
|
||||||
|
|
||||||
|
This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security
|
||||||
|
|
||||||
|
"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld
|
||||||
|
|||||||
44
docker-compose.beta.yml
Normal file
44
docker-compose.beta.yml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
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
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
services:
|
services:
|
||||||
wg-easy:
|
wg-easy:
|
||||||
build:
|
build:
|
||||||
dockerfile: ./Dockerfile
|
dockerfile: ./Dockerfile.dev
|
||||||
command: npm run serve
|
command: dev
|
||||||
volumes:
|
volumes:
|
||||||
- ./src/:/app/
|
- ./src/:/app/
|
||||||
|
- temp:/app/.nuxt/
|
||||||
|
- temp1:/app/node_modules/
|
||||||
|
- /lib/modules:/lib/modules:ro
|
||||||
# - ./data/:/etc/wireguard
|
# - ./data/:/etc/wireguard
|
||||||
ports:
|
ports:
|
||||||
- "51820:51820/udp"
|
- "51820:51820/udp"
|
||||||
@@ -13,5 +16,15 @@ services:
|
|||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
- SYS_MODULE
|
- SYS_MODULE
|
||||||
environment:
|
environment:
|
||||||
# - PASSWORD_HASH=p
|
- INIT_ENABLED=true
|
||||||
- WG_HOST=192.168.1.233
|
- INIT_HOST=test
|
||||||
|
- INIT_PORT=51820
|
||||||
|
- INIT_USERNAME=testtest
|
||||||
|
- INIT_PASSWORD=Qweasdyxcv!2
|
||||||
|
|
||||||
|
# folders should be generated inside container
|
||||||
|
volumes:
|
||||||
|
temp:
|
||||||
|
driver: local
|
||||||
|
temp1:
|
||||||
|
driver: local
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
# Change Language:
|
# Change Language:
|
||||||
# (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi)
|
# (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi)
|
||||||
# - LANG=de
|
- LANG=de
|
||||||
# ⚠️ Required:
|
# ⚠️ Required:
|
||||||
# Change this to your host's public address
|
# Change this to your host's public address
|
||||||
- WG_HOST=192.168.11.3
|
- WG_HOST=raspberrypi.local
|
||||||
|
|
||||||
# Optional:
|
# Optional:
|
||||||
- PASSWORD_HASH=$$2a$$12$$ZwvA8aZH/3JCrRr448G2rOy0Tb8xHHfV4wjN6Q33eWVACKdfMAgRq
|
# - PASSWORD_HASH=$$2y$$10$$hBCoykrB95WSzuV4fafBzOHWKu9sbyVa34GJr8VV5R/pIelfEMYyG # (needs double $$, hash of 'foobar123'; see "How_to_generate_an_bcrypt_hash.md" for generate the hash)
|
||||||
# (needs double $$, see "How_to_generate_an_bcrypt_hash.md" for generate the hash)
|
|
||||||
# - PORT=51821
|
# - PORT=51821
|
||||||
# - WG_PORT=51820
|
# - WG_PORT=51820
|
||||||
# - WG_CONFIG_PORT=92820
|
# - WG_CONFIG_PORT=92820
|
||||||
@@ -30,7 +29,7 @@ services:
|
|||||||
# - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart)
|
# - 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:14
|
||||||
container_name: wireguard
|
container_name: wg-easy
|
||||||
volumes:
|
volumes:
|
||||||
- etc_wireguard:/etc/wireguard
|
- etc_wireguard:/etc/wireguard
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
5
docs/.prettierrc.json
Normal file
5
docs/.prettierrc.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"1": "Initial version. Enjoy!",
|
|
||||||
"2": "You can now rename a client & update the address. Enjoy!",
|
|
||||||
"3": "Many improvements and small changes. Enjoy!",
|
|
||||||
"4": "Now with pretty charts for client's network speed. Enjoy!",
|
|
||||||
"5": "Many small improvements & feature requests. Enjoy!",
|
|
||||||
"6": "Many small performance improvements & bug fixes. Enjoy!",
|
|
||||||
"7": "Improved the look & performance of the upload/download chart.",
|
|
||||||
"8": "Updated to Node.js v18.",
|
|
||||||
"9": "Fixed issue running on devices with older kernels.",
|
|
||||||
"10": "Added sessionless HTTP API auth & automatic dark mode.",
|
|
||||||
"11": "Multilanguage Support & various bugfixes.",
|
|
||||||
"12": "UI_TRAFFIC_STATS, Import json configurations with no PreShared-Key, allow clients with no privateKey & more.",
|
|
||||||
"13": "New framework (h3), UI_CHART_TYPE, some bugfixes & more.",
|
|
||||||
"14": "Home Assistent support, PASSWORD_HASH (inc. Helper), translation updates bugfixes & more."
|
|
||||||
}
|
|
||||||
43
docs/content/advanced/api.md
Normal file
43
docs/content/advanced/api.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
|
|
||||||
|
There is no documentation for the API yet, but this will be added as the underlying library supports it.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
To use the API, you need to authenticate using Basic Authentication. The username and password are the same as the ones you use to log in to the web application.
|
||||||
|
If you use 2FA, the API will not work. You need to disable 2FA in the web application to use the API.
|
||||||
|
|
||||||
|
### Authentication Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
|
url = "https://example.com:51821/api/client"
|
||||||
|
response = requests.get(url, auth=HTTPBasicAuth('username', 'password'))
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
print(data)
|
||||||
|
else:
|
||||||
|
print(f"Error: {response.status_code}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
The Endpoints are not yet documented. But as file-based routing is used, you can find the endpoints in the `src/server/api` folder. The method is defined in the file name.
|
||||||
|
|
||||||
|
### Endpoints Example
|
||||||
|
|
||||||
|
| File Name | Endpoint | Method |
|
||||||
|
| -------------------------------- | -------------- | ------ |
|
||||||
|
| `src/server/api/client.get.ts` | `/api/client` | GET |
|
||||||
|
| `src/server/api/setup/2.post.ts` | `/api/setup/2` | POST |
|
||||||
11
docs/content/advanced/config/optional-config.md
Normal file
11
docs/content/advanced/config/optional-config.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
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 |
|
||||||
32
docs/content/advanced/config/unattended-setup.md
Normal file
32
docs/content/advanced/config/unattended-setup.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title: Unattended Setup
|
||||||
|
---
|
||||||
|
|
||||||
|
If you want to run the setup without any user interaction, e.g. with a tool like Ansible, you can use these environment variables to configure the setup.
|
||||||
|
|
||||||
|
These will only be used during the first start of the container. After that, the setup will be disabled.
|
||||||
|
|
||||||
|
| Env | Example | Description | Group |
|
||||||
|
| ---------------- | ----------------- | --------------------------------------------------------- | ----- |
|
||||||
|
| `INIT_ENABLED` | `true` | Enables the below env vars | 0 |
|
||||||
|
| `INIT_USERNAME` | `admin` | Sets admin username | 1 |
|
||||||
|
| `INIT_PASSWORD` | `Se!ureP%ssw` | Sets admin password | 1 |
|
||||||
|
| `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 1 |
|
||||||
|
| `INIT_PORT` | `51820` | Port clients will connect to and wireguard will listen on | 1 |
|
||||||
|
| `INIT_DNS` | `1.1.1.1,8.8.8.8` | Sets global dns setting | 2 |
|
||||||
|
| `INIT_IPV4_CIDR` | `10.8.0.0/24` | Sets IPv4 cidr | 3 |
|
||||||
|
| `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 3 |
|
||||||
|
|
||||||
|
/// warning | Variables have to be used together
|
||||||
|
|
||||||
|
If variables are in the same group, you have to set all of them. For example, if you set `INIT_IPV4_CIDR`, you also have to set `INIT_IPV6_CIDR`.
|
||||||
|
|
||||||
|
If you want to skip the setup process, you have to configure group `1`
|
||||||
|
///
|
||||||
|
|
||||||
|
/// note | Security
|
||||||
|
|
||||||
|
The initial username and password is not checked for complexity. Make sure to set a long enough username and password. Otherwise, the user won't be able to log in.
|
||||||
|
|
||||||
|
It's recommended to remove the variables after the setup is done to prevent the password from being exposed.
|
||||||
|
///
|
||||||
42
docs/content/advanced/metrics/prometheus.md
Normal file
42
docs/content/advanced/metrics/prometheus.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
title: Prometheus
|
||||||
|
---
|
||||||
|
|
||||||
|
To monitor the WireGuard server, you can use [Prometheus](https://prometheus.io/) and [Grafana](https://grafana.com/). The container exposes a `/metrics/prometheus` endpoint that can be scraped by Prometheus.
|
||||||
|
|
||||||
|
## Enable Prometheus
|
||||||
|
|
||||||
|
To enable Prometheus metrics, go to Admin Panel > General and enable Prometheus.
|
||||||
|
|
||||||
|
You can optionally set a Bearer Password for the metrics endpoints. This is useful if you want to expose the metrics endpoint to the internet.
|
||||||
|
|
||||||
|
## Configure Prometheus
|
||||||
|
|
||||||
|
You need to add a scrape config to your Prometheus configuration file. Here is an example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'wg-easy'
|
||||||
|
scrape_interval: 30s
|
||||||
|
metrics_path: /metrics/prometheus
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- 'localhost:51821'
|
||||||
|
authorization:
|
||||||
|
type: Bearer
|
||||||
|
credentials: 'SuperSecurePassword'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Grafana Dashboard
|
||||||
|
|
||||||
|
You can use the following Grafana dashboard to visualize the metrics:
|
||||||
|
|
||||||
|
[](https://grafana.com/grafana/dashboards/21733-wireguard/)
|
||||||
|
|
||||||
|
[21733](https://grafana.com/grafana/dashboards/21733-wireguard/)
|
||||||
|
|
||||||
|
/// note | Unofficial
|
||||||
|
|
||||||
|
The Grafana dashboard is not official and is not maintained by the `wg-easy` team. If you have any issues with the dashboard, please contact the author of the dashboard.
|
||||||
|
See [#1299](https://github.com/wg-easy/wg-easy/pull/1299) for more information.
|
||||||
|
///
|
||||||
52
docs/content/advanced/migrate/from-14-to-15.md
Normal file
52
docs/content/advanced/migrate/from-14-to-15.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
title: Migrate from v14 to v15
|
||||||
|
---
|
||||||
|
|
||||||
|
This guide will help you migrate from `v14` to version `v15` of `wg-easy`.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- This is a complete rewrite of the `wg-easy` project. Therefore the configuration files and the way you interact with the project have changed.
|
||||||
|
- If you use armv6 or armv7, you can't migrate to `v15` yet. We are working on it.
|
||||||
|
- If you are connecting to the web ui via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container.
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
### Backup
|
||||||
|
|
||||||
|
Before you start the migration, make sure to backup your existing configuration files.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### Remove old container
|
||||||
|
|
||||||
|
1. Stop the running container
|
||||||
|
|
||||||
|
If you are using `docker run`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker stop wg-easy
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using `docker-compose`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start new container
|
||||||
|
|
||||||
|
Follow the instructions in the [Getting Started][docs-getting-started] or [Basic Installation][docs-examples] guide to start the new container.
|
||||||
|
|
||||||
|
In the setup wizard, select that you already already have a configuration file and upload the `wg0.json` file you downloaded in the backup step.
|
||||||
|
|
||||||
|
[docs-getting-started]: ../../getting-started.md
|
||||||
|
[docs-examples]: ../../examples/tutorials/basic-installation.md
|
||||||
|
|
||||||
|
### Done
|
||||||
|
|
||||||
|
You have now successfully migrated to `v15` of `wg-easy`.
|
||||||
7
docs/content/advanced/migrate/index.md
Normal file
7
docs/content/advanced/migrate/index.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Migrate
|
||||||
|
---
|
||||||
|
|
||||||
|
If you want to migrate from an older version of `wg-easy` to the new version, you can find the migration guides listed below.
|
||||||
|
|
||||||
|
- [Migrate from v14 to v15](./from-14-to-15.md) : This guide should also work for any version before `v14`.
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
23
docs/content/contributing/general.md
Normal file
23
docs/content/contributing/general.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
title: General Information
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coding Style
|
||||||
|
|
||||||
|
When refactoring, writing or altering files, adhere to these rules:
|
||||||
|
|
||||||
|
1. **Adjust your style of coding to the style that is already present**! Even if you do not like it, this is due to consistency. There was a lot of work involved in making all files consistent.
|
||||||
|
2. **Use `pnpm lint` to check your scripts**! Your contributions are checked by GitHub Actions too, so you will need to do this.
|
||||||
|
3. **Use the provided `.vscode/settings.json`** file.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Make sure to select `edge` in the dropdown menu at the top. Navigate to the page you would like to edit and click the edit button in the top right. This allows you to make changes and create a pull-request.
|
||||||
|
|
||||||
|
Alternatively you can make the changes locally. For that you'll need to have Docker installed. Run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm docs:serve
|
||||||
|
```
|
||||||
|
|
||||||
|
This serves the documentation on your local machine on port `8080`. Each change will be hot-reloaded onto the page you view, just edit, save and look at the result.
|
||||||
58
docs/content/contributing/issues-and-pull-requests.md
Normal file
58
docs/content/contributing/issues-and-pull-requests.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
title: Issues and Pull Requests
|
||||||
|
---
|
||||||
|
|
||||||
|
This project is Open Source. That means that you can contribute on enhancements, bug fixing or improving the documentation.
|
||||||
|
|
||||||
|
## Opening an Issue
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
|
||||||
|
Maintainers take the time to improve on this project and help by solving issues together. It is therefore expected from others to make an effort and **comply with the rules**.
|
||||||
|
|
||||||
|
### Filing a Bug Report
|
||||||
|
|
||||||
|
Thank you for participating in this project and reporting a bug. `wg-easy` is a community-driven project, and each contribution counts!
|
||||||
|
|
||||||
|
Maintainers and moderators are volunteers. We greatly appreciate reports that take the time to provide detailed information via the template, enabling us to help you in the best and quickest way. Ignoring the template provided may seem easier, but discourages receiving any support.
|
||||||
|
|
||||||
|
Markdown formatting can be used in almost all text fields (_unless stated otherwise in the description_).
|
||||||
|
|
||||||
|
Be as precise as possible, and if in doubt, it's best to add more information that too few.
|
||||||
|
|
||||||
|
When an option is marked with "not officially supported" / "unsupported", then support is dependent on availability from specific maintainers.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
|
||||||
|
The development workflow is the following:
|
||||||
|
|
||||||
|
1. Fork the project
|
||||||
|
2. Write the code that is needed :D
|
||||||
|
3. Document your improvements if necessary
|
||||||
|
4. [Commit][commit] (and [sign your commit][gpg]), push and create a pull-request to merge into `master`. Please **use the pull-request template** to provide a minimum of contextual information and make sure to meet the requirements of the checklist.
|
||||||
|
|
||||||
|
Pull requests are automatically tested against the CI and will be reviewed when tests pass. When your changes are validated, your branch is merged. CI builds the new `:edge` image on every push to the `master` branch and your changes will be included in the next version release.
|
||||||
|
|
||||||
|
[docs-latest]: https://wg-easy.github.io/wg-easy/latest
|
||||||
|
[github-file-readme]: https://github.com/wg-easy/wg-easy/blob/master/README.md
|
||||||
|
[commit]: https://help.github.com/articles/closing-issues-via-commit-messages/
|
||||||
|
[gpg]: https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key
|
||||||
9
docs/content/examples/tutorials/adguard.md
Normal file
9
docs/content/examples/tutorials/adguard.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: AdGuard Home
|
||||||
|
---
|
||||||
|
|
||||||
|
It seems like the Docs on how to setup AdGuard Home are not available yet.
|
||||||
|
|
||||||
|
Feel free to create a PR and add them here.
|
||||||
|
|
||||||
|
<!-- TODO -->
|
||||||
72
docs/content/examples/tutorials/auto-updates.md
Normal file
72
docs/content/examples/tutorials/auto-updates.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
title: Auto Updates
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker Compose
|
||||||
|
|
||||||
|
With Docker Compose `wg-easy` can be updated with a single command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/wg-easy
|
||||||
|
sudo docker compose up -d --pull always
|
||||||
|
```
|
||||||
|
|
||||||
|
### Watchtower
|
||||||
|
|
||||||
|
If you want the updates to be fully automatic you can install Watchtower. This will check for updates every day at 4:00 AM and update the container if a new version is available.
|
||||||
|
|
||||||
|
File: `/etc/docker/containers/watchtower/docker-compose.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
watchtower:
|
||||||
|
image: containrrr/watchtower:latest
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
env_file:
|
||||||
|
- watchtower.env
|
||||||
|
restart: unless-stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
File: `/etc/docker/containers/watchtower/watchtower.env`
|
||||||
|
|
||||||
|
```env
|
||||||
|
WATCHTOWER_CLEANUP=true
|
||||||
|
WATCHTOWER_SCHEDULE=0 0 4 * * *
|
||||||
|
TZ=Europe/Berlin
|
||||||
|
|
||||||
|
# Email
|
||||||
|
# WATCHTOWER_NOTIFICATIONS_LEVEL=info
|
||||||
|
# WATCHTOWER_NOTIFICATIONS=email
|
||||||
|
# WATCHTOWER_NOTIFICATION_EMAIL_FROM=mail@example.com
|
||||||
|
# WATCHTOWER_NOTIFICATION_EMAIL_TO=mail@example.com
|
||||||
|
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.example.com
|
||||||
|
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=mail@example.com
|
||||||
|
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD="SuperSecurePassword"
|
||||||
|
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/watchtower
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker Run
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo docker stop wg-easy
|
||||||
|
sudo docker rm wg-easy
|
||||||
|
sudo docker pull ghcr.io/wg-easy/wg-easy
|
||||||
|
```
|
||||||
|
|
||||||
|
And then run the `docker run -d \ ...` command from [Docker Run][docker-run] again.
|
||||||
|
|
||||||
|
[docker-run]: ./docker-run.md
|
||||||
|
|
||||||
|
## Podman
|
||||||
|
|
||||||
|
To update `wg-easy` (and every container that has auto updates enabled), you can run the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo podman auto-update
|
||||||
|
```
|
||||||
67
docs/content/examples/tutorials/basic-installation.md
Normal file
67
docs/content/examples/tutorials/basic-installation.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
title: Basic Installation
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- TOOD: 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)
|
||||||
|
4. You need curl installed on your host
|
||||||
|
|
||||||
|
## Install Docker
|
||||||
|
|
||||||
|
Follow the Docs here: <https://docs.docker.com/engine/install/> and install Docker on your host.
|
||||||
|
|
||||||
|
## Install `wg-easy`
|
||||||
|
|
||||||
|
1. Create a directory for the configuration files (you can choose any directory you like):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo mkdir -p /etc/docker/containers/wg-easy
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Download docker compose file
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start `wg-easy`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/wg-easy
|
||||||
|
sudo docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup Firewall
|
||||||
|
|
||||||
|
If you are using a firewall, you need to open the following ports:
|
||||||
|
|
||||||
|
- UDP 51820 (WireGuard)
|
||||||
|
|
||||||
|
These ports can be changed, so if you change them you have to update your firewall rules accordingly.
|
||||||
|
|
||||||
|
## Setup Reverse Proxy
|
||||||
|
|
||||||
|
- To setup traefik follow the instructions here: [Traefik](./traefik.md)
|
||||||
|
- To setup caddy follow the instructions here: [Caddy](./caddy.md)
|
||||||
|
- If you do not want to use a reverse proxy follow the instructions here: [No Reverse Proxy](./reverse-proxyless.md)
|
||||||
|
|
||||||
|
## Update `wg-easy`
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Auto Update
|
||||||
|
|
||||||
|
If you want to enable auto-updates, follow the instructions here: [Auto Updates][auto-updates]
|
||||||
|
|
||||||
|
[auto-updates]: ./auto-updates.md
|
||||||
9
docs/content/examples/tutorials/caddy.md
Normal file
9
docs/content/examples/tutorials/caddy.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Caddy
|
||||||
|
---
|
||||||
|
|
||||||
|
It seems like the Docs on how to setup Caddy are not available yet.
|
||||||
|
|
||||||
|
Feel free to create a PR and add them here.
|
||||||
|
|
||||||
|
<!-- TODO -->
|
||||||
41
docs/content/examples/tutorials/docker-run.md
Normal file
41
docs/content/examples/tutorials/docker-run.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
title: Docker Run
|
||||||
|
---
|
||||||
|
|
||||||
|
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 \
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- ref: major version -->
|
||||||
|
|
||||||
|
To automatically install & run `wg-easy`, simply run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -d \
|
||||||
|
--net wg \
|
||||||
|
-e INSECURE=true \
|
||||||
|
--name wg-easy \
|
||||||
|
--ip6 fdcc:ad94:bacf:61a3::2a \
|
||||||
|
--ip 10.42.42.42 \
|
||||||
|
-v ~/.wg-easy:/etc/wireguard \
|
||||||
|
-v /lib/modules:/lib/modules:ro \
|
||||||
|
-p 51820:51820/udp \
|
||||||
|
-p 51821:51821/tcp \
|
||||||
|
--cap-add NET_ADMIN \
|
||||||
|
--cap-add SYS_MODULE \
|
||||||
|
--sysctl net.ipv4.ip_forward=1 \
|
||||||
|
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
|
||||||
|
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
|
||||||
|
--sysctl net.ipv6.conf.all.forwarding=1 \
|
||||||
|
--sysctl net.ipv6.conf.default.forwarding=1 \
|
||||||
|
--restart unless-stopped \
|
||||||
|
ghcr.io/wg-easy/wg-easy:15
|
||||||
|
```
|
||||||
|
|
||||||
|
The Web UI will now be available at <http://0.0.0.0:51821>.
|
||||||
7
docs/content/examples/tutorials/dockerless.md
Normal file
7
docs/content/examples/tutorials/dockerless.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Without Docker
|
||||||
|
---
|
||||||
|
|
||||||
|
This is currently not yet supported.
|
||||||
|
|
||||||
|
<!-- TODO -->
|
||||||
108
docs/content/examples/tutorials/podman-nft.md
Normal file
108
docs/content/examples/tutorials/podman-nft.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
title: Podman + nftables
|
||||||
|
---
|
||||||
|
|
||||||
|
This guide will show you how to run `wg-easy` with rootful Podman and nftables.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
1. Podman installed with version 4.4 or higher
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Create a Folder for the configuration files:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo mkdir -p /etc/containers/systemd/wg-easy
|
||||||
|
sudo mkdir -p /etc/containers/volumes/wg-easy
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a file `/etc/containers/systemd/wg-easy/wg-easy.container` with the following content:
|
||||||
|
|
||||||
|
<!-- ref: major version -->
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Container]
|
||||||
|
ContainerName=wg-easy
|
||||||
|
Image=ghcr.io/wg-easy/wg-easy:15
|
||||||
|
AutoUpdate=registry
|
||||||
|
|
||||||
|
Volume=/etc/containers/volumes/wg-easy:/etc/wireguard:Z
|
||||||
|
Network=wg-easy.network
|
||||||
|
PublishPort=51820:51820/udp
|
||||||
|
PublishPort=51821:51821/tcp
|
||||||
|
|
||||||
|
# this is used to allow access over HTTP
|
||||||
|
# remove this when using a reverse proxy
|
||||||
|
Environment=INSECURE=true
|
||||||
|
|
||||||
|
AddCapability=NET_ADMIN
|
||||||
|
AddCapability=SYS_MODULE
|
||||||
|
AddCapability=NET_RAW
|
||||||
|
Sysctl=net.ipv4.ip_forward=1
|
||||||
|
Sysctl=net.ipv4.conf.all.src_valid_mark=1
|
||||||
|
Sysctl=net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
Sysctl=net.ipv6.conf.all.forwarding=1
|
||||||
|
Sysctl=net.ipv6.conf.default.forwarding=1
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
# this is used to start the container on boot
|
||||||
|
WantedBy=default.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a file `/etc/containers/systemd/wg-easy/wg-easy.network` with the following content:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Network]
|
||||||
|
NetworkName=wg-easy
|
||||||
|
IPv6=true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Load Kernel Modules
|
||||||
|
|
||||||
|
You will need to load the following kernel modules
|
||||||
|
|
||||||
|
```txt
|
||||||
|
wireguard
|
||||||
|
nft_masq
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a file `/etc/modules-load.d/wg-easy.conf` with the following content:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
wireguard
|
||||||
|
nft_masq
|
||||||
|
```
|
||||||
|
|
||||||
|
## Start the Container
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl start wg-easy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Edit Hooks
|
||||||
|
|
||||||
|
In the Admin Panel of your WireGuard server, go to the `Hooks` tab and add the following hook:
|
||||||
|
|
||||||
|
1. PostUp
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nft add table inet wg_table; nft add chain inet wg_table prerouting { type nat hook prerouting priority 100 \; }; nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \; }; nft add rule inet wg_table postrouting ip saddr {{ipv4Cidr}} oifname {{device}} masquerade; nft add rule inet wg_table postrouting ip6 saddr {{ipv6Cidr}} oifname {{device}} masquerade; nft add chain inet wg_table input { type filter hook input priority 0 \; policy accept \; }; nft add rule inet wg_table input udp dport {{port}} accept; nft add rule inet wg_table input tcp dport {{uiPort}} accept; nft add chain inet wg_table forward { type filter hook forward priority 0 \; policy accept \; }; nft add rule inet wg_table forward iifname "wg0" accept; nft add rule inet wg_table forward oifname "wg0" accept;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. PostDown
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nft delete table inet wg_table
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't have iptables loaded on your server, you could see many errors in the logs or in the UI. You can ignore them.
|
||||||
|
|
||||||
|
## Restart the Container
|
||||||
|
|
||||||
|
Restart the container to apply the new hooks:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo systemctl restart wg-easy
|
||||||
|
```
|
||||||
29
docs/content/examples/tutorials/reverse-proxyless.md
Normal file
29
docs/content/examples/tutorials/reverse-proxyless.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
title: No Reverse Proxy
|
||||||
|
---
|
||||||
|
|
||||||
|
/// warning | Insecure
|
||||||
|
|
||||||
|
This is insecure. You should use a reverse proxy to secure the connection.
|
||||||
|
|
||||||
|
Only use this method if you know what you are doing.
|
||||||
|
///
|
||||||
|
|
||||||
|
If you only allow access to the web UI from your local network, you can skip the reverse proxy setup. This is not recommended, but it is possible.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
- Edit the `docker-compose.yml` file and uncomment `environment` and `INSECURE`
|
||||||
|
|
||||||
|
- Set `INSECURE` to `true` to allow access to the web UI over a non-secure connection.
|
||||||
|
|
||||||
|
- The `docker-compose.yml` file should look something like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- INSECURE=true
|
||||||
|
```
|
||||||
|
|
||||||
|
- Save the file and restart `wg-easy`.
|
||||||
|
|
||||||
|
- Make sure that the Web UI is not accessible from outside your local network.
|
||||||
184
docs/content/examples/tutorials/traefik.md
Normal file
184
docs/content/examples/tutorials/traefik.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
---
|
||||||
|
title: Traefik
|
||||||
|
---
|
||||||
|
|
||||||
|
/// note | Opinionated
|
||||||
|
|
||||||
|
This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths.
|
||||||
|
///
|
||||||
|
|
||||||
|
## Create docker compose project
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo mkdir -p /etc/docker/containers/traefik
|
||||||
|
cd /etc/docker/containers/traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create docker compose file
|
||||||
|
|
||||||
|
File: `/etc/docker/containers/traefik/docker-compose.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
image: traefik:3.3
|
||||||
|
container_name: traefik
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '80:80'
|
||||||
|
- '443:443/tcp'
|
||||||
|
- '443:443/udp'
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- /etc/docker/volumes/traefik/traefik.yml:/traefik.yml:ro
|
||||||
|
- /etc/docker/volumes/traefik/traefik_dynamic.yml:/traefik_dynamic.yml:ro
|
||||||
|
- /etc/docker/volumes/traefik/acme.json:/acme.json
|
||||||
|
networks:
|
||||||
|
- traefik
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik:
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create traefik.yml
|
||||||
|
|
||||||
|
File: `/etc/docker/volumes/traefik/traefik.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
log:
|
||||||
|
level: INFO
|
||||||
|
|
||||||
|
entryPoints:
|
||||||
|
web:
|
||||||
|
address: ':80/tcp'
|
||||||
|
http:
|
||||||
|
redirections:
|
||||||
|
entryPoint:
|
||||||
|
to: websecure
|
||||||
|
scheme: https
|
||||||
|
websecure:
|
||||||
|
address: ':443/tcp'
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
- compress@file
|
||||||
|
- hsts@file
|
||||||
|
tls:
|
||||||
|
certResolver: letsencrypt
|
||||||
|
http3: {}
|
||||||
|
|
||||||
|
api:
|
||||||
|
dashboard: true
|
||||||
|
|
||||||
|
certificatesResolvers:
|
||||||
|
letsencrypt:
|
||||||
|
acme:
|
||||||
|
email: $mail@example.com$
|
||||||
|
storage: acme.json
|
||||||
|
httpChallenge:
|
||||||
|
entryPoint: web
|
||||||
|
|
||||||
|
providers:
|
||||||
|
docker:
|
||||||
|
watch: true
|
||||||
|
network: traefik
|
||||||
|
exposedByDefault: false
|
||||||
|
file:
|
||||||
|
filename: traefik_dynamic.yml
|
||||||
|
|
||||||
|
serversTransport:
|
||||||
|
insecureSkipVerify: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create traefik_dynamic.yml
|
||||||
|
|
||||||
|
File: `/etc/docker/volumes/traefik/traefik_dynamic.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
services:
|
||||||
|
basicAuth:
|
||||||
|
users:
|
||||||
|
- '$username$:$password$'
|
||||||
|
compress:
|
||||||
|
compress: {}
|
||||||
|
hsts:
|
||||||
|
headers:
|
||||||
|
stsSeconds: 2592000
|
||||||
|
routers:
|
||||||
|
api:
|
||||||
|
rule: Host(`traefik.$example.com$`)
|
||||||
|
entrypoints:
|
||||||
|
- websecure
|
||||||
|
middlewares:
|
||||||
|
- services
|
||||||
|
service: api@internal
|
||||||
|
|
||||||
|
tls:
|
||||||
|
options:
|
||||||
|
default:
|
||||||
|
cipherSuites:
|
||||||
|
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||||
|
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||||
|
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||||
|
sniStrict: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create acme.json
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo touch /etc/docker/volumes/traefik/acme.json
|
||||||
|
sudo chmod 600 /etc/docker/volumes/traefik/acme.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create network
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo docker network create traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
## Start traefik
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
You can no access the Traefik dashboard at `https://traefik.$example.com$` with the credentials you set in `traefik_dynamic.yml`.
|
||||||
|
|
||||||
|
## Add Labels to `wg-easy`
|
||||||
|
|
||||||
|
To add labels to your `wg-easy` service, you can add the following to your `docker-compose.yml` file:
|
||||||
|
|
||||||
|
File: `/etc/docker/containers/wg-easy/docker-compose.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
wg-easy:
|
||||||
|
...
|
||||||
|
container_name: wg-easy
|
||||||
|
networks:
|
||||||
|
...
|
||||||
|
traefik: {}
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.wg-easy.rule=Host(`wg-easy.$example.com$`)"
|
||||||
|
- "traefik.http.routers.wg-easy.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.wg-easy.service=wg-easy"
|
||||||
|
- "traefik.http.services.wg-easy.loadbalancer.server.port=51821"
|
||||||
|
...
|
||||||
|
|
||||||
|
networks:
|
||||||
|
...
|
||||||
|
traefik:
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Restart `wg-easy`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/wg-easy
|
||||||
|
sudo docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now access `wg-easy` at `https://wg-easy.$example.com$` and start the setup.
|
||||||
97
docs/content/faq.md
Normal file
97
docs/content/faq.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
title: FAQ
|
||||||
|
hide:
|
||||||
|
- navigation
|
||||||
|
---
|
||||||
|
|
||||||
|
Here are some frequently asked questions or errors about `wg-easy`. If you have a question that is not answered here, please feel free to open a discussion on GitHub.
|
||||||
|
|
||||||
|
## Error: WireGuard exited with the error: Cannot find device "wg0"
|
||||||
|
|
||||||
|
This error indicates that the WireGuard interface `wg0` does not exist. This can happen if the WireGuard kernel module is not loaded or if the interface was not created properly.
|
||||||
|
|
||||||
|
To resolve this issue, you can try the following steps:
|
||||||
|
|
||||||
|
1. **Load the WireGuard kernel module**: If the WireGuard kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo modprobe wireguard
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Load the WireGuard kernel module on boot**: If you want to ensure that the WireGuard kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "wireguard" | sudo tee -a /etc/modules
|
||||||
|
```
|
||||||
|
|
||||||
|
## can't initialize iptables table `nat': Table does not exist (do you need to insmod?)
|
||||||
|
|
||||||
|
This error indicates that the `nat` table in `iptables` does not exist. This can happen if the `iptables` kernel module is not loaded or if the `nat` table is not supported by your kernel.
|
||||||
|
|
||||||
|
To resolve this issue, you can try the following steps:
|
||||||
|
|
||||||
|
1. **Load the `nat` kernel module**: If the `nat` kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo modprobe iptable_nat
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Load the `nat` kernel module on boot**: If you want to ensure that the `nat` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "iptable_nat" | sudo tee -a /etc/modules
|
||||||
|
```
|
||||||
|
|
||||||
|
## can't initialize ip6tables table `nat': Table does not exist (do you need to insmod?)
|
||||||
|
|
||||||
|
This error indicates that the `nat` table in `ip6tables` does not exist. This can happen if the `ip6tables` kernel module is not loaded or if the `nat` table is not supported by your kernel.
|
||||||
|
|
||||||
|
To resolve this issue, you can try the following steps:
|
||||||
|
|
||||||
|
1. **Load the `nat` kernel module**: If the `nat` kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo modprobe ip6table_nat
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Load the `nat` kernel module on boot**: If you want to ensure that the `nat` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "ip6table_nat" | sudo tee -a /etc/modules
|
||||||
|
```
|
||||||
|
|
||||||
|
## can't initialize iptables table `filter': Permission denied
|
||||||
|
|
||||||
|
This error indicates that the `filter` table in `iptables` cannot be initialized due to permission issues. This can happen if you are not running the command with sufficient privileges.
|
||||||
|
|
||||||
|
To resolve this issue, you can try the following steps:
|
||||||
|
|
||||||
|
1. **Load the `filter` kernel module**: If the `filter` kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo modprobe iptable_filter
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Load the `filter` kernel module on boot**: If you want to ensure that the `filter` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "iptable_filter" | sudo tee -a /etc/modules
|
||||||
|
```
|
||||||
|
|
||||||
|
## can't initialize ip6tables table `filter': Permission denied
|
||||||
|
|
||||||
|
This error indicates that the `filter` table in `ip6tables` cannot be initialized due to permission issues. This can happen if you are not running the command with sufficient privileges.
|
||||||
|
|
||||||
|
To resolve this issue, you can try the following steps:
|
||||||
|
|
||||||
|
1. **Load the `filter` kernel module**: If the `filter` kernel module is not loaded, you can load it manually by running:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo modprobe ip6table_filter
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Load the `filter` kernel module on boot**: If you want to ensure that the `filter` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "ip6table_filter" | sudo tee -a /etc/modules
|
||||||
|
```
|
||||||
72
docs/content/getting-started.md
Normal file
72
docs/content/getting-started.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
title: Getting Started
|
||||||
|
hide:
|
||||||
|
- navigation
|
||||||
|
---
|
||||||
|
|
||||||
|
This page explains how to get started with `wg-easy`. The guide uses Docker Compose as a reference. In our examples, we mount the named volume `etc_wireguard` to `/etc/wireguard` inside the container.
|
||||||
|
|
||||||
|
## Preliminary Steps
|
||||||
|
|
||||||
|
Before you can get started with deploying your own VPN, there are some requirements to be met:
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
### Host Setup
|
||||||
|
|
||||||
|
There are a few requirements for a suitable host system:
|
||||||
|
|
||||||
|
1. You need to have a container runtime installed
|
||||||
|
|
||||||
|
/// note | About the Container Runtime
|
||||||
|
|
||||||
|
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/
|
||||||
|
[docker-compose-installation]: https://docs.docker.com/compose/install/
|
||||||
|
[docker-compose-specification]: https://docs.docker.com/compose/compose-file/
|
||||||
|
[docs-podman]: ./examples/tutorials/podman-nft.md
|
||||||
|
|
||||||
|
## Deploying the Actual Image
|
||||||
|
|
||||||
|
### Tagging Convention
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
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 |
|
||||||
|
|
||||||
|
<!-- ref: major version -->
|
||||||
|
|
||||||
|
When publishing a tag we follow the [Semantic Versioning][semver] specification. The `latest` tag is always pointing to the latest stable release. If you want to avoid breaking changes, use the major version tag (e.g. `15`).
|
||||||
|
|
||||||
|
[github-ci]: https://github.com/wg-easy/wg-easy/actions
|
||||||
|
[ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy
|
||||||
|
[semver]: https://semver.org/
|
||||||
|
|
||||||
|
### Follow tutorials
|
||||||
|
|
||||||
|
- [Basic Installation with Docker Compose (Recommended)](./examples/tutorials/basic-installation.md)
|
||||||
|
- [Simple Installation with Docker Run](./examples/tutorials/docker-run.md)
|
||||||
|
- [Advanced Installation with Podman](./examples/tutorials/podman-nft.md)
|
||||||
|
|
||||||
|
/// 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**.
|
||||||
26
docs/content/guides/2fa.md
Normal file
26
docs/content/guides/2fa.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: 2FA
|
||||||
|
---
|
||||||
|
|
||||||
|
The user can enable 2FA from the Account page. The Account page is accessible from the dropdown menu in the top right corner of the application.
|
||||||
|
|
||||||
|
## Enable TOTP
|
||||||
|
|
||||||
|
- **Enable Two Factor Authentication**: Enable TOTP for the user.
|
||||||
|
|
||||||
|
## Configure TOTP
|
||||||
|
|
||||||
|
A QR code will be displayed. Scan the QR code with your TOTP application (e.g., Google Authenticator, Authy, etc.) to add the account.
|
||||||
|
|
||||||
|
To verify that the TOTP key is working, the user must enter the TOTP code generated by the TOTP application.
|
||||||
|
|
||||||
|
- **TOTP Key**: The TOTP key for the user. This key is used to generate the TOTP code.
|
||||||
|
- **TOTP Code**: The current TOTP code for the user. This code is used to verify the TOTP key.
|
||||||
|
- **Enable Two Factor Authentication**: Enable TOTP for the user.
|
||||||
|
|
||||||
|
## Disable TOTP
|
||||||
|
|
||||||
|
To disable TOTP, the user must enter the current password.
|
||||||
|
|
||||||
|
- **Current Password**: The current password of the user.
|
||||||
|
- **Disable Two Factor Authentication**: Disable TOTP for the user.
|
||||||
5
docs/content/guides/admin.md
Normal file
5
docs/content/guides/admin.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: Admin Panel
|
||||||
|
---
|
||||||
|
|
||||||
|
TODO
|
||||||
43
docs/content/guides/cli.md
Normal file
43
docs/content/guides/cli.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
title: CLI
|
||||||
|
---
|
||||||
|
|
||||||
|
If you want to use the CLI, you can run it with
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/wg-easy
|
||||||
|
docker compose exec -it wg-easy cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Run
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run --rm -it \
|
||||||
|
-v ~/.wg-easy:/etc/wireguard \
|
||||||
|
ghcr.io/wg-easy/wg-easy:15 \
|
||||||
|
cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reset Password
|
||||||
|
|
||||||
|
If you want to reset the password for the admin user, you can run the following command:
|
||||||
|
|
||||||
|
#### By Prompt
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/wg-easy
|
||||||
|
docker compose exec -it wg-easy cli db:admin:reset
|
||||||
|
```
|
||||||
|
|
||||||
|
You are asked to provide the new password
|
||||||
|
|
||||||
|
#### By Argument
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /etc/docker/containers/wg-easy
|
||||||
|
docker compose exec -it wg-easy cli db:admin:reset --password <new_password>
|
||||||
|
```
|
||||||
|
|
||||||
|
This will reset the password for the admin user to the new password you provided. If you include special characters in the password, make sure to escape them properly.
|
||||||
50
docs/content/guides/clients.md
Normal file
50
docs/content/guides/clients.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
title: Edit Client
|
||||||
|
---
|
||||||
|
|
||||||
|
## General
|
||||||
|
|
||||||
|
- **Name**: The name of the client.
|
||||||
|
- **Enabled**: Whether the client can connect to the VPN.
|
||||||
|
- **Expire Date**: The date the client will be disabled.
|
||||||
|
|
||||||
|
## Address
|
||||||
|
|
||||||
|
- **IPv4**: The IPv4 address of the client.
|
||||||
|
- **IPv6**: The IPv6 address of the client.
|
||||||
|
|
||||||
|
## Allowed IPs
|
||||||
|
|
||||||
|
Which IPs will be routed through the VPN.
|
||||||
|
|
||||||
|
This will not prevent the user from modifying it locally and accessing IP ranges that they should not be able to access.
|
||||||
|
|
||||||
|
Use firewall rules to prevent access to IP ranges that the user should not be able to access.
|
||||||
|
|
||||||
|
## Server Allowed IPs
|
||||||
|
|
||||||
|
Which IPs will be routed to the client.
|
||||||
|
|
||||||
|
## DNS
|
||||||
|
|
||||||
|
The DNS server that the client will use.
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
- **MTU**: The maximum transmission unit for the client.
|
||||||
|
- **Persistent Keepalive**: The interval for sending keepalive packets to the server.
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
This can only be used for clients that use `wg-quick`. Setting this will throw a error when importing the config on other clients.
|
||||||
|
|
||||||
|
- **PreUp**: Commands to run before the interface is brought up.
|
||||||
|
- **PostUp**: Commands to run after the interface is brought up.
|
||||||
|
- **PreDown**: Commands to run before the interface is brought down.
|
||||||
|
- **PostDown**: Commands to run after the interface is brought down.
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
- **Save**: Save the changes made in the form.
|
||||||
|
- **Revert**: Revert the changes made in the form.
|
||||||
|
- **Delete**: Delete the client.
|
||||||
24
docs/content/guides/setup.md
Normal file
24
docs/content/guides/setup.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: Setup
|
||||||
|
---
|
||||||
|
|
||||||
|
## User Setup
|
||||||
|
|
||||||
|
- **Username**: The username of the user.
|
||||||
|
- **Password**: The password of the user.
|
||||||
|
- **Confirm Password**: The password of the user.
|
||||||
|
|
||||||
|
## Existing Setup
|
||||||
|
|
||||||
|
If you have the config from the previous version, you can import it by clicking "Yes". This currently expects a config from v14.
|
||||||
|
|
||||||
|
If this is the first time you are using this, you can click "No" to create a new config.
|
||||||
|
|
||||||
|
### No - Host Setup
|
||||||
|
|
||||||
|
- **Host**: The host of the server. The clients will connect to this address. This can be a domain name or an IP address. Make sure to wrap it in brackets if it is an IPv6 address. For example: `[::1]` or `[2001:db8::1]`.
|
||||||
|
- **Port**: The port of the server. The clients will connect to this port. The server will listen on this port.
|
||||||
|
|
||||||
|
### Yes - Migration
|
||||||
|
|
||||||
|
Select the `wg0.json` file from the previous version. Read [Migrate from v14 to v15](../advanced/migrate/from-14-to-15.md) for more information.
|
||||||
41
docs/content/index.md
Normal file
41
docs/content/index.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
title: Home
|
||||||
|
hide:
|
||||||
|
- navigation
|
||||||
|
---
|
||||||
|
|
||||||
|
# Welcome to the Documentation for `wg-easy`
|
||||||
|
|
||||||
|
/// 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].
|
||||||
|
///
|
||||||
|
|
||||||
|
This documentation provides you not only with the basic setup and configuration of `wg-easy` but also with advanced configuration, elaborate usage scenarios, detailed examples, hints and more.
|
||||||
|
|
||||||
|
[docs-tagging]: ./getting-started.md#tagging-convention
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
`wg-easy` is the easiest way to run WireGuard VPN + Web-based Admin UI.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
If you're new to wg-easy, make sure to read the [_Getting Started_ chapter][docs-getting-started] first. If you want to look at examples for Docker Run and Compose, we have an [_Examples_ page][docs-examples].
|
||||||
|
|
||||||
|
[docs-getting-started]: ./getting-started.md
|
||||||
|
[docs-examples]: ./examples/tutorials/basic-installation.md
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
We are always happy to welcome new contributors. For guidelines and entrypoints please have a look at the [Contributing section][docs-contributing].
|
||||||
|
|
||||||
|
[docs-contributing]: ./contributing/issues-and-pull-requests.md
|
||||||
|
|
||||||
|
### Migration
|
||||||
|
|
||||||
|
If you are migrating from an older version of `wg-easy`, please read the [_Migration_ chapter][docs-migration].
|
||||||
|
|
||||||
|
[docs-migration]: ./advanced/migrate/from-14-to-15.md
|
||||||
87
docs/mkdocs.yml
Normal file
87
docs/mkdocs.yml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
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
|
||||||
4
docs/requirements.txt
Normal file
4
docs/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
mkdocs-material
|
||||||
|
pillow
|
||||||
|
cairosvg
|
||||||
|
mike
|
||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "wg-easy",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"version": "1.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
package.json
20
package.json
@@ -1,10 +1,16 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.1",
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"sudobuild": "DOCKER_BUILDKIT=1 sudo docker build --tag wg-easy .",
|
"dev": "docker compose -f docker-compose.dev.yml up wg-easy --build",
|
||||||
"build": "DOCKER_BUILDKIT=1 docker build --tag wg-easy .",
|
"cli:dev": "docker compose -f docker-compose.dev.yml run --build --rm -it wg-easy cli:dev",
|
||||||
"serve": "docker compose -f docker-compose.yml -f docker-compose.dev.yml up",
|
"build": "docker build -t wg-easy .",
|
||||||
"sudostart": "sudo docker run --env WG_HOST=0.0.0.0 --name wg-easy --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy",
|
"docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8080",
|
||||||
"start": "docker run --env WG_HOST=0.0.0.0 --name wg-easy --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy"
|
"scripts:version": "bash scripts/version.sh",
|
||||||
}
|
"format:check:docs": "prettier --check docs"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^3.5.3"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.10.0"
|
||||||
}
|
}
|
||||||
24
pnpm-lock.yaml
generated
Normal file
24
pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.5.3
|
||||||
|
version: 3.5.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
prettier@3.5.3:
|
||||||
|
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
prettier@3.5.3: {}
|
||||||
54
scripts/version.sh
Normal file
54
scripts/version.sh
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
package_json="src/package.json"
|
||||||
|
|
||||||
|
# Function to update the version in package.json
|
||||||
|
update_version() {
|
||||||
|
local new_version=$1
|
||||||
|
jq --arg new_version "$new_version" '.version = $new_version' $package_json > tmp.json && mv tmp.json $package_json
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the current version from package.json
|
||||||
|
current_version=$(jq -r '.version' $package_json)
|
||||||
|
echo "Current version: $current_version"
|
||||||
|
|
||||||
|
# Prompt the user for the new version
|
||||||
|
read -p "Enter the new version (following SemVer): " new_version
|
||||||
|
|
||||||
|
# Official SemVer regex for validation
|
||||||
|
semver_regex="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||||
|
|
||||||
|
# Validate the new version
|
||||||
|
if ! echo "$new_version" | grep -Eq "$semver_regex"; then
|
||||||
|
echo "Invalid version format. Please use SemVer format (e.g., 1.0.0 or 1.0.0-alpha)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update the version in package.json
|
||||||
|
update_version $new_version
|
||||||
|
echo "Updated package.json to version $new_version"
|
||||||
|
|
||||||
|
echo "----"
|
||||||
|
echo "If you changed the major version, remember to update the docker-compose.yml file and docs (search for: ref: major version)"
|
||||||
|
echo "----"
|
||||||
|
|
||||||
|
echo "If you did everything press 'y' to commit the changes and create a new tag"
|
||||||
|
read -p "Do you want to continue? (y/n): " confirm
|
||||||
|
|
||||||
|
if [ "$confirm" != "y" ]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit the changes
|
||||||
|
git add $package_json
|
||||||
|
git commit -m "Bump version to $new_version"
|
||||||
|
echo "Committed the changes"
|
||||||
|
|
||||||
|
# Create a new Git tag
|
||||||
|
git tag -a "v$new_version" -m "Release version $new_version"
|
||||||
|
echo "Created Git tag v$new_version"
|
||||||
|
|
||||||
|
# Push the commit & tag to the remote repository
|
||||||
|
git push origin master --follow-tags
|
||||||
|
echo "Pushed Git commit and tag v$new_version to remote repository"
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "athom",
|
|
||||||
"ignorePatterns": [
|
|
||||||
"**/vendor/*.js"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"consistent-return": "off",
|
|
||||||
"no-shadow": "off",
|
|
||||||
"max-len": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
26
src/.gitignore
vendored
Normal file
26
src/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
wg-easy.db
|
||||||
2
src/.prettierignore
Normal file
2
src/.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pnpm-lock.yaml
|
||||||
|
server/database/migrations/meta
|
||||||
7
src/.prettierrc.json
Normal file
7
src/.prettierrc.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
|
}
|
||||||
57
src/app/app.vue
Normal file
57
src/app/app.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<ToastProvider>
|
||||||
|
<NuxtLayout>
|
||||||
|
<NuxtPage />
|
||||||
|
<ToastViewport
|
||||||
|
class="fixed bottom-0 right-0 z-[2147483647] m-0 flex w-[390px] max-w-[100vw] list-none flex-col gap-[10px] p-[var(--viewport-padding)] outline-none [--viewport-padding:_25px]"
|
||||||
|
>
|
||||||
|
<BaseToast ref="toastRef" />
|
||||||
|
</ToastViewport>
|
||||||
|
</NuxtLayout>
|
||||||
|
</ToastProvider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const toast = useToast();
|
||||||
|
const toastRef = useTemplateRef('toastRef');
|
||||||
|
toast.setToast(toastRef);
|
||||||
|
|
||||||
|
// make sure to fetch release early
|
||||||
|
useGlobalStore();
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
bodyAttrs: {
|
||||||
|
class: 'bg-gray-50 dark:bg-neutral-800',
|
||||||
|
},
|
||||||
|
link: [
|
||||||
|
{
|
||||||
|
rel: 'manifest',
|
||||||
|
href: '/manifest.json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
type: 'image/png',
|
||||||
|
href: '/favicon.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'apple-touch-icon',
|
||||||
|
href: '/apple-touch-icon.png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
name: 'mobile-web-app-capable',
|
||||||
|
content: 'yes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'apple-mobile-web-app-capable',
|
||||||
|
content: 'yes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'apple-mobile-web-app-status-bar-style',
|
||||||
|
content: 'black-translucent',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
title: 'WireGuard',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
34
src/app/components/Admin/CidrDialog.vue
Normal file
34
src/app/components/Admin/CidrDialog.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<BaseDialog :trigger-class="triggerClass">
|
||||||
|
<template #trigger><slot /></template>
|
||||||
|
<template #title>{{ $t('admin.interface.changeCidr') }}</template>
|
||||||
|
<template #description>
|
||||||
|
<FormGroup>
|
||||||
|
<FormTextField id="ipv4Cidr" v-model="ipv4Cidr" label="IPv4" />
|
||||||
|
<FormTextField id="ipv6Cidr" v-model="ipv6Cidr" label="IPv6" />
|
||||||
|
</FormGroup>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton @click="$emit('change', ipv4Cidr, ipv6Cidr)">
|
||||||
|
{{ $t('dialog.change') }}
|
||||||
|
</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
</template>
|
||||||
|
</BaseDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineEmits(['change']);
|
||||||
|
const props = defineProps<{
|
||||||
|
triggerClass?: string;
|
||||||
|
ipv4Cidr: string;
|
||||||
|
ipv6Cidr: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const ipv4Cidr = ref(props.ipv4Cidr);
|
||||||
|
const ipv6Cidr = ref(props.ipv6Cidr);
|
||||||
|
</script>
|
||||||
24
src/app/components/Admin/RestartInterfaceDialog.vue
Normal file
24
src/app/components/Admin/RestartInterfaceDialog.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<BaseDialog :trigger-class="triggerClass">
|
||||||
|
<template #trigger><slot /></template>
|
||||||
|
<template #title>{{ $t('admin.interface.restart') }}</template>
|
||||||
|
<template #description>
|
||||||
|
{{ $t('admin.interface.restartWarn') }}
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton @click="$emit('restart')">
|
||||||
|
{{ $t('admin.interface.restart') }}
|
||||||
|
</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
</template>
|
||||||
|
</BaseDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineEmits(['restart']);
|
||||||
|
defineProps<{ triggerClass?: string }>();
|
||||||
|
</script>
|
||||||
39
src/app/components/Admin/SuggestDialog.vue
Normal file
39
src/app/components/Admin/SuggestDialog.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<BaseDialog :trigger-class="triggerClass">
|
||||||
|
<template #trigger><slot /></template>
|
||||||
|
<template #title>{{ $t('admin.config.suggest') }}</template>
|
||||||
|
<template #description>
|
||||||
|
<div class="flex flex-col items-start gap-2">
|
||||||
|
<p>{{ $t('admin.config.suggestDesc') }}</p>
|
||||||
|
<p v-if="!data">
|
||||||
|
{{ $t('general.loading') }}
|
||||||
|
</p>
|
||||||
|
<BaseSelect v-else v-model="selected" :options="data" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton @click="$emit('change', selected)">
|
||||||
|
{{ $t('dialog.change') }}
|
||||||
|
</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
</template>
|
||||||
|
</BaseDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineEmits(['change']);
|
||||||
|
const props = defineProps<{
|
||||||
|
triggerClass?: string;
|
||||||
|
url: '/api/admin/ip-info' | '/api/setup/4';
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { data } = useFetch(props.url, {
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
|
||||||
|
const selected = ref<string>();
|
||||||
|
</script>
|
||||||
20
src/app/components/Base/Avatar.vue
Normal file
20
src/app/components/Base/Avatar.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<AvatarRoot
|
||||||
|
class="mr-2 inline-flex select-none items-center justify-center overflow-hidden rounded-full align-middle"
|
||||||
|
>
|
||||||
|
<AvatarImage
|
||||||
|
class="h-full w-full rounded-[inherit] object-cover"
|
||||||
|
:src="img ?? ''"
|
||||||
|
/>
|
||||||
|
<AvatarFallback
|
||||||
|
class="leading-1 flex h-full w-full items-center justify-center bg-white text-sm font-medium"
|
||||||
|
:delay-ms="600"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AvatarFallback>
|
||||||
|
</AvatarRoot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ img?: string }>();
|
||||||
|
</script>
|
||||||
26
src/app/components/Base/Button.vue
Normal file
26
src/app/components/Base/Button.vue
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="elementType"
|
||||||
|
role="button"
|
||||||
|
class="inline-flex items-center rounded border-2 border-gray-100 px-4 py-2 text-gray-700 transition hover:border-red-800 hover:bg-red-800 hover:text-white dark:border-neutral-600 dark:text-neutral-200"
|
||||||
|
v-bind="attrs"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
as: {
|
||||||
|
type: String,
|
||||||
|
default: 'button',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const elementType = computed(() => props.as);
|
||||||
|
|
||||||
|
const attrs = computed(() => {
|
||||||
|
const { as, ...attrs } = props;
|
||||||
|
return attrs;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
20
src/app/components/Base/Chart.vue
Normal file
20
src/app/components/Base/Chart.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<ClientOnly>
|
||||||
|
<apexchart
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:options="options"
|
||||||
|
:series="series"
|
||||||
|
/>
|
||||||
|
</ClientOnly>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { VueApexChartsComponent } from 'vue3-apexcharts';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
options: VueApexChartsComponent['options'];
|
||||||
|
series: VueApexChartsComponent['series'];
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
31
src/app/components/Base/Dialog.vue
Normal file
31
src/app/components/Base/Dialog.vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<DialogRoot :modal="true">
|
||||||
|
<DialogTrigger :class="triggerClass"><slot name="trigger" /></DialogTrigger>
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay
|
||||||
|
class="fixed inset-0 z-30 bg-gray-500 opacity-75 dark:bg-black dark:opacity-50"
|
||||||
|
/>
|
||||||
|
<DialogContent
|
||||||
|
class="fixed left-1/2 top-1/2 z-[100] max-h-[85vh] w-[90vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md bg-white p-6 shadow-2xl focus:outline-none dark:bg-neutral-700"
|
||||||
|
>
|
||||||
|
<DialogTitle
|
||||||
|
class="m-0 text-lg font-semibold text-gray-900 dark:text-neutral-200"
|
||||||
|
>
|
||||||
|
<slot name="title" />
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription
|
||||||
|
class="mb-5 mt-2 text-sm leading-normal text-gray-500 dark:text-neutral-300"
|
||||||
|
>
|
||||||
|
<slot name="description" />
|
||||||
|
</DialogDescription>
|
||||||
|
<div class="mt-6 flex justify-end gap-2">
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</DialogPortal>
|
||||||
|
</DialogRoot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ triggerClass?: string }>();
|
||||||
|
</script>
|
||||||
10
src/app/components/Base/Input.vue
Normal file
10
src/app/components/Base/Input.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<input
|
||||||
|
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<unknown>();
|
||||||
|
</script>
|
||||||
37
src/app/components/Base/Select.vue
Normal file
37
src/app/components/Base/Select.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<SelectRoot v-model="selected">
|
||||||
|
<SelectTrigger
|
||||||
|
class="inline-flex h-8 items-center justify-around gap-2 rounded bg-gray-200 px-3 text-sm leading-none dark:bg-neutral-500 dark:text-neutral-200"
|
||||||
|
aria-label="Choose option"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select..." />
|
||||||
|
<IconsArrowDown class="size-3" />
|
||||||
|
</SelectTrigger>
|
||||||
|
|
||||||
|
<SelectPortal>
|
||||||
|
<SelectContent
|
||||||
|
class="z-[100] min-w-28 rounded bg-gray-300 dark:bg-neutral-500"
|
||||||
|
>
|
||||||
|
<SelectViewport class="p-2">
|
||||||
|
<SelectItem
|
||||||
|
v-for="(option, index) in options"
|
||||||
|
:key="index"
|
||||||
|
:value="option.value"
|
||||||
|
class="relative flex h-6 items-center rounded px-3 text-sm leading-none outline-none hover:bg-red-800 hover:text-white dark:text-white"
|
||||||
|
>
|
||||||
|
<SelectItemText>
|
||||||
|
{{ option.value }} - {{ option.label }}
|
||||||
|
</SelectItemText>
|
||||||
|
</SelectItem>
|
||||||
|
</SelectViewport>
|
||||||
|
</SelectContent>
|
||||||
|
</SelectPortal>
|
||||||
|
</SelectRoot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{
|
||||||
|
options: { label: string; value: string }[];
|
||||||
|
}>();
|
||||||
|
const selected = defineModel<string>();
|
||||||
|
</script>
|
||||||
17
src/app/components/Base/Switch.vue
Normal file
17
src/app/components/Base/Switch.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<SwitchRoot
|
||||||
|
:id="id"
|
||||||
|
v-model:checked="data"
|
||||||
|
:name="id"
|
||||||
|
class="relative flex h-6 w-10 cursor-default rounded-full bg-gray-200 shadow-sm focus-within:outline focus-within:outline-red-700 data-[state=checked]:bg-red-800 dark:bg-neutral-400"
|
||||||
|
>
|
||||||
|
<SwitchThumb
|
||||||
|
class="my-auto block h-4 w-4 translate-x-1 rounded-full bg-white shadow-sm transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[20px]"
|
||||||
|
/>
|
||||||
|
</SwitchRoot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ id?: string }>();
|
||||||
|
const data = defineModel<boolean>();
|
||||||
|
</script>
|
||||||
46
src/app/components/Base/Toast.vue
Normal file
46
src/app/components/Base/Toast.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<ToastRoot
|
||||||
|
v-for="(e, i) in count"
|
||||||
|
:key="i"
|
||||||
|
:class="[
|
||||||
|
`grid grid-cols-[auto_max-content] items-center gap-x-3 rounded-md p-3 text-neutral-200 shadow-lg [grid-template-areas:_'title_action'_'description_action'] data-[swipe=cancel]:translate-x-0 data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)]`,
|
||||||
|
{
|
||||||
|
'bg-green-800': e.type === 'success',
|
||||||
|
'bg-red-800': e.type === 'error',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<ToastTitle class="mb-1 text-sm font-medium [grid-area:_title]">
|
||||||
|
{{ e.title }}
|
||||||
|
</ToastTitle>
|
||||||
|
<ToastDescription class="m-0 text-sm [grid-area:_description]">{{
|
||||||
|
e.message
|
||||||
|
}}</ToastDescription>
|
||||||
|
<ToastAction as-child alt-text="toast" class="[grid-area:_action]">
|
||||||
|
<slot />
|
||||||
|
</ToastAction>
|
||||||
|
<ToastClose aria-label="Close">
|
||||||
|
<span aria-hidden>×</span>
|
||||||
|
</ToastClose>
|
||||||
|
</ToastRoot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
ToastAction,
|
||||||
|
ToastClose,
|
||||||
|
ToastDescription,
|
||||||
|
ToastRoot,
|
||||||
|
ToastTitle,
|
||||||
|
} from 'radix-vue';
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
publish,
|
||||||
|
});
|
||||||
|
|
||||||
|
const count = reactive<ToastParams[]>([]);
|
||||||
|
|
||||||
|
function publish(e: ToastParams) {
|
||||||
|
count.push({ type: e.type, title: e.title, message: e.message });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
29
src/app/components/Base/Tooltip.vue
Normal file
29
src/app/components/Base/Tooltip.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<TooltipProvider>
|
||||||
|
<TooltipRoot :open="open" @update:open="open = $event">
|
||||||
|
<TooltipTrigger
|
||||||
|
class="mx-2 inline-flex h-4 w-4 items-center justify-center rounded-full text-gray-400 outline-none focus:shadow-sm focus:shadow-black"
|
||||||
|
as-child
|
||||||
|
>
|
||||||
|
<button type="button" @click="open = !open">
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent
|
||||||
|
class="select-none whitespace-pre-line rounded bg-gray-600 px-3 py-2 text-sm leading-none text-white shadow-lg will-change-[transform,opacity]"
|
||||||
|
:side-offset="5"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
<TooltipArrow class="fill-gray-600" :width="8" />
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
</TooltipRoot>
|
||||||
|
</TooltipProvider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ text: string }>();
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
</script>
|
||||||
11
src/app/components/ClientCard/Address.vue
Normal file
11
src/app/components/ClientCard/Address.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<span class="inline-block">
|
||||||
|
{{ client.ipv4Address }}, {{ client.ipv6Address }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
30
src/app/components/ClientCard/Avatar.vue
Normal file
30
src/app/components/ClientCard/Avatar.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<div class="relative mt-2 h-10 w-10 self-start rounded-full bg-gray-50">
|
||||||
|
<BaseAvatar :img="client.avatar" class="h-10 w-10">
|
||||||
|
<IconsAvatar class="h-6 w-6 text-gray-300" />
|
||||||
|
</BaseAvatar>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
isPeerConnected({
|
||||||
|
latestHandshakeAt: client.latestHandshakeAt
|
||||||
|
? new Date(client.latestHandshakeAt)
|
||||||
|
: null,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute -bottom-1 -right-1 h-4 w-4 animate-ping rounded-full bg-red-100 p-1 dark:bg-red-100"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="absolute bottom-0 right-0 h-2 w-2 rounded-full bg-red-800 dark:bg-red-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
136
src/app/components/ClientCard/Charts.vue
Normal file
136
src/app/components/ClientCard/Charts.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="`absolute bottom-0 left-0 right-0 z-0 h-6 ${globalStore.uiChartType === 'line' && 'line-chart'}`"
|
||||||
|
>
|
||||||
|
<BaseChart :options="chartOptionsTX" :series="client.transferTxSeries" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="`absolute left-0 right-0 top-0 z-0 h-6 ${globalStore.uiChartType === 'line' && 'line-chart'}`"
|
||||||
|
>
|
||||||
|
<BaseChart
|
||||||
|
:options="chartOptionsRX"
|
||||||
|
:series="client.transferRxSeries"
|
||||||
|
style="transform: scaleY(-1)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ApexOptions } from 'apexcharts';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const chartOptionsTX = computed(() => {
|
||||||
|
const opts = {
|
||||||
|
...chartOptions,
|
||||||
|
colors: [CHART_COLORS.tx[theme.value]],
|
||||||
|
};
|
||||||
|
opts.chart.type = globalStore.uiChartType;
|
||||||
|
opts.stroke.width = UI_CHART_PROPS[globalStore.uiChartType].strokeWidth;
|
||||||
|
return opts;
|
||||||
|
});
|
||||||
|
|
||||||
|
const chartOptionsRX = computed(() => {
|
||||||
|
const opts = {
|
||||||
|
...chartOptions,
|
||||||
|
colors: [CHART_COLORS.rx[theme.value]],
|
||||||
|
};
|
||||||
|
opts.chart.type = globalStore.uiChartType;
|
||||||
|
opts.stroke.width = UI_CHART_PROPS[globalStore.uiChartType].strokeWidth;
|
||||||
|
return opts;
|
||||||
|
});
|
||||||
|
|
||||||
|
const chartOptions = {
|
||||||
|
chart: {
|
||||||
|
type: undefined as ApexChart['type'],
|
||||||
|
background: 'transparent',
|
||||||
|
stacked: false,
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
parentHeightOffset: 0,
|
||||||
|
sparkline: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: [],
|
||||||
|
stroke: {
|
||||||
|
curve: 'smooth',
|
||||||
|
width: 0,
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: 'gradient',
|
||||||
|
gradient: {
|
||||||
|
shade: 'dark',
|
||||||
|
type: 'vertical',
|
||||||
|
shadeIntensity: 0,
|
||||||
|
gradientToColors: CHART_COLORS.gradient[theme.value],
|
||||||
|
inverseColors: false,
|
||||||
|
opacityTo: 0,
|
||||||
|
stops: [0, 100],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: false,
|
||||||
|
padding: {
|
||||||
|
left: -10,
|
||||||
|
right: 0,
|
||||||
|
bottom: -15,
|
||||||
|
top: -15,
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
lines: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies ApexOptions;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="css">
|
||||||
|
.line-chart .apexcharts-svg {
|
||||||
|
transform: translateY(3px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
51
src/app/components/ClientCard/ClientCard.vue
Normal file
51
src/app/components/ClientCard/ClientCard.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<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"
|
||||||
|
>
|
||||||
|
<div class="flex w-full items-center gap-3 md:gap-4">
|
||||||
|
<ClientCardAvatar :client="client" />
|
||||||
|
<div class="flex w-full flex-col gap-2 xxs:flex-row">
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<ClientCardAddress :client="client" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ClientCardLastSeen :client="client" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ClientCardOneTimeLink :client="client" />
|
||||||
|
<ClientCardExpireDate :client="client" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="mt-px flex shrink-0 items-center justify-end gap-2 text-xs text-gray-400 dark:text-neutral-400"
|
||||||
|
>
|
||||||
|
<ClientCardTransfer :client="client" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end">
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between gap-1 text-gray-400 dark:text-neutral-400"
|
||||||
|
>
|
||||||
|
<ClientCardSwitch :client="client" />
|
||||||
|
<ClientCardEdit :client="client" />
|
||||||
|
<ClientCardQRCode :client="client" />
|
||||||
|
<ClientCardConfig :client="client" />
|
||||||
|
<ClientCardOneTimeLinkBtn :client="client" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
16
src/app/components/ClientCard/Config.vue
Normal file
16
src/app/components/ClientCard/Config.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<a
|
||||||
|
:href="'/api/client/' + client.id + '/configuration'"
|
||||||
|
download
|
||||||
|
class="inline-block rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
|
||||||
|
:title="$t('client.downloadConfig')"
|
||||||
|
>
|
||||||
|
<IconsDownload class="w-5" />
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
14
src/app/components/ClientCard/Edit.vue
Normal file
14
src/app/components/ClientCard/Edit.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<NuxtLink
|
||||||
|
class="rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
|
||||||
|
:to="`/clients/${client.id}`"
|
||||||
|
>
|
||||||
|
<IconsEdit class="w-5" />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
23
src/app/components/ClientCard/ExpireDate.vue
Normal file
23
src/app/components/ClientCard/ExpireDate.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="block pb-1 text-xs text-gray-500 md:inline-block md:pb-0 dark:text-neutral-400"
|
||||||
|
>
|
||||||
|
<span class="inline-block">{{ expiredDateFormat(client.expiresAt) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{ client: LocalClient }>();
|
||||||
|
|
||||||
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
|
function expiredDateFormat(value: string | null) {
|
||||||
|
if (value === null) return t('client.permanent');
|
||||||
|
const dateTime = new Date(value);
|
||||||
|
return dateTime.toLocaleDateString(locale.value, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
16
src/app/components/ClientCard/LastSeen.vue
Normal file
16
src/app/components/ClientCard/LastSeen.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<span
|
||||||
|
v-if="client.latestHandshakeAt"
|
||||||
|
:title="$t('client.lastSeen') + $d(new Date(client.latestHandshakeAt))"
|
||||||
|
>
|
||||||
|
{{ timeago(new Date(client.latestHandshakeAt)) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { format as timeago } from 'timeago.js';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
16
src/app/components/ClientCard/Name.vue
Normal file
16
src/app/components/ClientCard/Name.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="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>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
51
src/app/components/ClientCard/OneTimeLink.vue
Normal file
51
src/app/components/ClientCard/OneTimeLink.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="client.oneTimeLink !== null" class="text-xs text-gray-400">
|
||||||
|
<a :href="'./cnf/' + client.oneTimeLink.oneTimeLink">{{ path }}</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{ client: LocalClient }>();
|
||||||
|
|
||||||
|
const path = ref('Loading...');
|
||||||
|
const timer = ref<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
const { localeProperties } = useI18n();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
timer.value = setIntervalImmediately(() => {
|
||||||
|
if (props.client.oneTimeLink === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeLeft =
|
||||||
|
new Date(props.client.oneTimeLink.expiresAt).getTime() - Date.now();
|
||||||
|
|
||||||
|
if (timeLeft <= 0) {
|
||||||
|
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (00:00)`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatter = new Intl.DateTimeFormat(localeProperties.value.language, {
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hourCycle: 'h23',
|
||||||
|
});
|
||||||
|
|
||||||
|
const minutes = Math.floor(timeLeft / 60000);
|
||||||
|
const seconds = Math.floor((timeLeft % 60000) / 1000);
|
||||||
|
|
||||||
|
const date = new Date(0);
|
||||||
|
date.setMinutes(minutes);
|
||||||
|
date.setSeconds(seconds);
|
||||||
|
|
||||||
|
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (${formatter.format(date)})`;
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer.value) {
|
||||||
|
clearTimeout(timer.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
32
src/app/components/ClientCard/OneTimeLinkBtn.vue
Normal file
32
src/app/components/ClientCard/OneTimeLinkBtn.vue
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<button
|
||||||
|
class="inline-block rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
|
||||||
|
:title="$t('client.otlDesc')"
|
||||||
|
@click="showOneTimeLink"
|
||||||
|
>
|
||||||
|
<IconsLink class="w-5" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{ client: LocalClient }>();
|
||||||
|
|
||||||
|
const clientsStore = useClientsStore();
|
||||||
|
|
||||||
|
const _showOneTimeLink = useSubmit(
|
||||||
|
`/api/client/${props.client.id}/generateOneTimeLink`,
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
revert: async () => {
|
||||||
|
await clientsStore.refresh();
|
||||||
|
},
|
||||||
|
noSuccessToast: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function showOneTimeLink() {
|
||||||
|
return _showOneTimeLink(undefined);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
16
src/app/components/ClientCard/QRCode.vue
Normal file
16
src/app/components/ClientCard/QRCode.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<ClientsQRCodeDialog :qr-code="`./api/client/${client.id}/qrcode.svg`">
|
||||||
|
<div
|
||||||
|
class="rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
|
||||||
|
:title="$t('client.showQR')"
|
||||||
|
>
|
||||||
|
<IconsQRCode class="w-5" />
|
||||||
|
</div>
|
||||||
|
</ClientsQRCodeDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
53
src/app/components/ClientCard/Switch.vue
Normal file
53
src/app/components/ClientCard/Switch.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<BaseSwitch
|
||||||
|
v-model="enabled"
|
||||||
|
:title="
|
||||||
|
client.enabled ? $t('client.disableClient') : $t('client.enableClient')
|
||||||
|
"
|
||||||
|
@click="toggleClient"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const enabled = ref(props.client.enabled);
|
||||||
|
|
||||||
|
const clientsStore = useClientsStore();
|
||||||
|
|
||||||
|
const _disableClient = useSubmit(
|
||||||
|
`/api/client/${props.client.id}/disable`,
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
revert: async () => {
|
||||||
|
await clientsStore.refresh();
|
||||||
|
},
|
||||||
|
noSuccessToast: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const _enableClient = useSubmit(
|
||||||
|
`/api/client/${props.client.id}/enable`,
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
revert: async () => {
|
||||||
|
await clientsStore.refresh();
|
||||||
|
},
|
||||||
|
noSuccessToast: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function toggleClient() {
|
||||||
|
if (props.client.enabled) {
|
||||||
|
await _disableClient(undefined);
|
||||||
|
} else {
|
||||||
|
await _enableClient(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
45
src/app/components/ClientCard/Transfer.vue
Normal file
45
src/app/components/ClientCard/Transfer.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<!-- Transfer TX -->
|
||||||
|
<div v-if="client.transferTx" class="min-w-20 md:min-w-24">
|
||||||
|
<span
|
||||||
|
class="flex gap-1"
|
||||||
|
:title="$t('client.totalDownload') + bytes(client.transferTx)"
|
||||||
|
>
|
||||||
|
<IconsArrowDown class="mt-0.5 inline h-3 align-middle" />
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-700 dark:text-neutral-200"
|
||||||
|
>{{ bytes(client.transferTxCurrent) }}/s</span
|
||||||
|
>
|
||||||
|
<!-- Total TX -->
|
||||||
|
<br /><span class="font-regular" style="font-size: 0.85em">{{
|
||||||
|
bytes(client.transferTx)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transfer RX -->
|
||||||
|
<div v-if="client.transferRx" class="min-w-20 md:min-w-24">
|
||||||
|
<span
|
||||||
|
class="flex gap-1"
|
||||||
|
:title="$t('client.totalUpload') + bytes(client.transferRx)"
|
||||||
|
>
|
||||||
|
<IconsArrowUp class="mt-0.5 inline h-3 align-middle" />
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-700 dark:text-neutral-200"
|
||||||
|
>{{ bytes(client.transferRxCurrent) }}/s</span
|
||||||
|
>
|
||||||
|
<!-- Total RX -->
|
||||||
|
<br /><span class="font-regular" style="font-size: 0.85em">{{
|
||||||
|
bytes(client.transferRx)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
client: LocalClient;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
53
src/app/components/Clients/CreateDialog.vue
Normal file
53
src/app/components/Clients/CreateDialog.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<BaseDialog :trigger-class="triggerClass">
|
||||||
|
<template #trigger>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
{{ $t('client.new') }}
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<FormTextField id="name" v-model="name" :label="$t('client.name')" />
|
||||||
|
<FormDateField
|
||||||
|
id="expiresAt"
|
||||||
|
v-model="expiresAt"
|
||||||
|
:label="$t('client.expireDate')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton @click="createClient">{{ $t('client.create') }}</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
</template>
|
||||||
|
</BaseDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const name = ref<string>('');
|
||||||
|
const expiresAt = ref<string | null>(null);
|
||||||
|
const clientsStore = useClientsStore();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
defineProps<{ triggerClass?: string }>();
|
||||||
|
|
||||||
|
function createClient() {
|
||||||
|
return _createClient({ name: name.value, expiresAt: expiresAt.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
const _createClient = useSubmit(
|
||||||
|
'/api/client',
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
revert: () => clientsStore.refresh(),
|
||||||
|
successMsg: t('client.created'),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
26
src/app/components/Clients/DeleteDialog.vue
Normal file
26
src/app/components/Clients/DeleteDialog.vue
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<BaseDialog :trigger-class="triggerClass">
|
||||||
|
<template #trigger><slot /></template>
|
||||||
|
<template #title>{{ $t('client.deleteClient') }}</template>
|
||||||
|
<template #description>
|
||||||
|
{{ $t('client.deleteDialog1') }}
|
||||||
|
<strong>{{ clientName }}</strong
|
||||||
|
>? {{ $t('client.deleteDialog2') }}
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<BaseButton @click="$emit('delete')">{{
|
||||||
|
$t('client.deleteClient')
|
||||||
|
}}</BaseButton>
|
||||||
|
</DialogClose>
|
||||||
|
</template>
|
||||||
|
</BaseDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineEmits(['delete']);
|
||||||
|
defineProps<{ triggerClass?: string; clientName: string }>();
|
||||||
|
</script>
|
||||||
11
src/app/components/Clients/Empty.vue
Normal file
11
src/app/components/Clients/Empty.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<p class="m-10 text-center text-sm text-gray-400 dark:text-neutral-400">
|
||||||
|
{{ $t('client.empty') }}<br /><br />
|
||||||
|
<ClientsCreateDialog>
|
||||||
|
<BaseButton as="span">
|
||||||
|
<IconsPlus class="w-4 md:mr-2" />
|
||||||
|
<span class="text-sm">{{ $t('client.new') }}</span>
|
||||||
|
</BaseButton>
|
||||||
|
</ClientsCreateDialog>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
13
src/app/components/Clients/List.vue
Normal file
13
src/app/components/Clients/List.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-for="client in clientsStore.clients"
|
||||||
|
:key="client.id"
|
||||||
|
class="relative overflow-hidden border-b border-solid border-gray-100 last:border-b-0 dark:border-neutral-600"
|
||||||
|
>
|
||||||
|
<ClientCard :client="client" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const clientsStore = useClientsStore();
|
||||||
|
</script>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user