Compare commits

...

23 Commits

Author SHA1 Message Date
Bernd Storath f79b0fd025 Bump version to 15.0.0 2025-05-28 12:22:42 +02:00
Bernd Storath d2ce82241b Bump version to 15.0.0-beta.13 2025-05-28 12:11:03 +02:00
Bernd Storath 8c395ec275 fix pre-release 2025-05-28 12:10:47 +02:00
Bernd Storath 10f42170f3 Bump version to 15.0.0-beta.13 2025-05-28 11:59:38 +02:00
Bernd Storath a8aa85bdaa Feat: map client to interface (#1886)
map client to interface
2025-05-28 11:58:33 +02:00
Bernd Storath 7e1aa5807d Feat: variants (#1885)
add primary & secondary button & actionfield
2025-05-28 11:44:16 +02:00
Bernd Storath df57921b8e update packages 2025-05-27 09:39:37 +02:00
Bernd Storath 4fbf059e61 add armv7 support
override until
- https://github.com/nuxt/nuxt/issues/32124
- https://github.com/nuxt-modules/i18n/issues/3605
are fixed
2025-05-16 09:11:50 +02:00
Bernd Storath b150e3f3b4 update packages 2025-05-16 08:54:52 +02:00
Bernd Storath aed10ab0bd update packages 2025-05-12 07:41:40 +02:00
Bernd Storath 478e7207b2 don't hoist libsql
not needed anymore
2025-05-05 11:15:24 +02:00
Bernd Storath c8dc710435 update packages 2025-05-05 10:25:28 +02:00
Bernd Storath 19d9e3b7d7 update packages
this greatly improves qr code size and quality
2025-04-26 16:49:51 +02:00
Bernd Storath c4efb1d03a update packages
argon2 now prebuilds for armv7
only libsql is missing: https://github.com/tursodatabase/libsql-js/pull/169
2025-04-24 11:50:35 +02:00
Bernd Storath 529b9eeb88 improve image size 2025-04-22 16:09:18 +02:00
Bernd Storath 69ee741d7e Feat: distributed build (#1829)
* distribute build across runners

* better formatting

* fix issues

* fix matrix

* retrigger build
2025-04-22 14:11:28 +02:00
Bernd Storath f2dc38e91b add setup guide 2025-04-22 10:47:55 +02:00
Bernd Storath 64e9484331 update packages 2025-04-22 07:46:33 +02:00
Bernd Storath c777fa30b3 publish stable docker compose (#1828) 2025-04-22 07:44:28 +02:00
Bernd Storath 734d91fd98 replace nightly with edge (#1819) 2025-04-17 15:58:45 +02:00
Bernd Storath 84ed7b299f Feat: Cli (#1818)
* add cli

* fix lint

* add docs, include cli packages

* fix docs, username instead of name
2025-04-16 14:17:02 +02:00
Bernd Storath 1cfe6404b2 Feat docs (#1814)
* improve docs and formatting

* lint in ci

avoid using bundled prettier from vscode extension

* fix action, typos

* remove header

* remove unused deps
2025-04-15 12:43:57 +02:00
Bernd Storath 2a32c1b9c0 remove unused dependencies 2025-04-14 08:36:07 +02:00
85 changed files with 4087 additions and 2506 deletions
-23
View File
@@ -1,23 +0,0 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# The JSON files contain newlines inconsistently
[*.json]
insert_final_newline = ignore
# Minified JavaScript files shouldn't be changed
[**.min.js]
indent_style = ignore
insert_final_newline = ignore
[*.md]
trim_trailing_whitespace = false
+100 -15
View File
@@ -4,21 +4,38 @@ on:
workflow_dispatch:
jobs:
docker:
name: Build & Deploy Docker
runs-on: ubuntu-latest
docker-build:
name: Build Docker
runs-on: ${{ matrix.arch.os }}
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
contents: read
strategy:
fail-fast: false
matrix:
arch:
- platform: linux/amd64
os: ubuntu-latest
- platform: linux/arm64
os: ubuntu-24.04-arm
- platform: linux/arm/v7
os: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Prepare
run: |
platform=${{ matrix.arch.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/wg-easy/wg-easy
flavor: |
latest=false
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@@ -27,15 +44,83 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & Publish Docker Image
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ghcr.io/wg-easy/wg-easy:development
cache-from: type=gha
cache-to: type=gha,mode=min
platforms: ${{ matrix.arch.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/wg-easy/wg-easy
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
docker-merge:
name: Merge & Deploy Docker
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
needs: docker-build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/wg-easy/wg-easy
flavor: |
latest=false
tags: |
type=raw,value=development
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
docs:
name: Build & Deploy Docs
@@ -43,7 +128,7 @@ jobs:
if: github.repository_owner == 'wg-easy'
permissions:
contents: write
needs: docker
needs: docker-merge
steps:
- uses: actions/checkout@v4
+167
View File
@@ -0,0 +1,167 @@
name: Edge
on:
workflow_dispatch:
push:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
docker-build:
name: Build Docker
runs-on: ${{ matrix.arch.os }}
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
strategy:
fail-fast: false
matrix:
arch:
- platform: linux/amd64
os: ubuntu-latest
- platform: linux/arm64
os: ubuntu-24.04-arm
- platform: linux/arm/v7
os: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Prepare
run: |
platform=${{ matrix.arch.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/wg-easy/wg-easy
flavor: |
latest=false
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: ${{ matrix.arch.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/wg-easy/wg-easy
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
docker-merge:
name: Merge & Deploy Docker
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
needs: docker-build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/wg-easy/wg-easy
flavor: |
latest=false
tags: |
type=raw,value=edge
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
docs:
name: Build & Deploy Docs
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
contents: write
needs: docker-merge
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.11.9
cache: "pip"
cache-dependency-path: docs/requirements.txt
- name: Install Dependencies
run: |
pip install -r docs/requirements.txt
- name: Setup Git User
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Build Docs Website
run: |
cd docs
git fetch origin gh-pages --depth=1 || true
mike deploy --push --update-aliases edge
-77
View File
@@ -1,77 +0,0 @@
name: Nightly
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
docker:
name: Build & Deploy Docker
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & Publish Docker Image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ghcr.io/wg-easy/wg-easy:nightly
cache-from: type=gha
cache-to: type=gha,mode=min
docs:
name: Build & Deploy Docs
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
contents: write
needs: docker
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.11.9
cache: "pip"
cache-dependency-path: docs/requirements.txt
- name: Install Dependencies
run: |
pip install -r docs/requirements.txt
- name: Setup Git User
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Build Docs Website
run: |
cd docs
git fetch origin gh-pages --depth=1 || true
mike deploy --push --update-aliases nightly
+18 -6
View File
@@ -11,14 +11,26 @@ concurrency:
jobs:
docker:
name: Build Docker
runs-on: ubuntu-latest
runs-on: ${{ matrix.arch.os }}
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
contents: read
strategy:
fail-fast: false
matrix:
arch:
- platform: linux/amd64
os: ubuntu-latest
- platform: linux/arm64
os: ubuntu-24.04-arm
- platform: linux/arm/v7
os: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Prepare
run: |
platform=${{ matrix.arch.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -37,7 +49,7 @@ jobs:
with:
context: .
push: false
platforms: linux/amd64,linux/arm64
platforms: ${{ matrix.arch.platform }}
tags: ghcr.io/wg-easy/wg-easy:pr
cache-from: type=gha
cache-to: type=gha,mode=min
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
+100 -25
View File
@@ -10,20 +10,103 @@ on:
# as this will break the latest and major tags
jobs:
docker:
name: Build & Deploy Docker
docker-build:
name: Build Docker
runs-on: ${{ matrix.arch.os }}
if: |
github.repository_owner == 'wg-easy' &&
startsWith(github.ref, 'refs/tags/v')
permissions:
packages: write
strategy:
fail-fast: false
matrix:
arch:
- platform: linux/amd64
os: ubuntu-latest
- platform: linux/arm64
os: ubuntu-24.04-arm
- platform: linux/arm/v7
os: ubuntu-24.04-arm
steps:
- uses: actions/checkout@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
if: |
github.repository_owner == 'wg-easy' &&
startsWith(github.ref, 'refs/tags/v')
permissions:
packages: write
contents: read
needs: docker-build
steps:
- uses: actions/checkout@v4
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -41,23 +124,15 @@ jobs:
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
- name: Build & Publish Docker Image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=min
- name: Inspect image
run: |
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
docs:
name: Build & Deploy Docs
@@ -67,7 +142,7 @@ jobs:
startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
needs: docker
needs: docker-merge
steps:
- uses: actions/checkout@v4
@@ -93,8 +168,8 @@ jobs:
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
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
+27
View File
@@ -7,9 +7,36 @@ on:
pull_request:
jobs:
docs:
name: Check Docs
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "lts/*"
check-latest: true
cache: "pnpm"
- name: Check docs formatting
run: |
pnpm install
pnpm format:check:docs
lint:
name: Lint
runs-on: ubuntu-latest
needs: docs
if: github.repository_owner == 'wg-easy'
strategy:
+1
View File
@@ -1,2 +1,3 @@
.DS_Store
*.swp
node_modules
+2 -1
View File
@@ -9,6 +9,7 @@
"yoavbls.pretty-ts-errors",
"bradlc.vscode-tailwindcss",
"vue.volar",
"lokalise.i18n-ally"
"lokalise.i18n-ally",
"DavidAnson.vscode-markdownlint"
]
}
+5
View File
@@ -18,6 +18,11 @@
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 4,
"editor.useTabStops": false
},
"typescript.tsdk": "./src/node_modules/typescript/lib",
"i18n-ally.enabledFrameworks": ["vue"],
"i18n-ally.localesPaths": ["src/i18n/locales"],
+3 -1
View File
@@ -27,11 +27,13 @@ As the whole setup has changed, we recommend to start from scratch. And import y
- SQLite Database
- Deprecated Dockerless Installations
- Added Docker Volume Mount (`/lib/modules`)
- Removed ARMv6 and ARMv7 support
- Removed ARMv6 support
- Connections over HTTP require setting the `INSECURE` env var
- Changed license from CC BY-NC-SA 4.0 to AGPL-3.0-only
- Added 2FA using TOTP
- Improved mobile support
- CLI
- Replaced `nightly` with `edge`
## [14.0.0] - 2024-09-04
+7 -2
View File
@@ -25,8 +25,13 @@ HEALTHCHECK --interval=1m --timeout=5s --retries=3 CMD /usr/bin/timeout 5s /bin/
COPY --from=build /app/.output /app
# Copy migrations
COPY --from=build /app/server/database/migrations /app/server/database/migrations
# libsql
RUN cd /app/server && npm install --no-save libsql
# libsql (https://github.com/nitrojs/nitro/issues/3328)
RUN cd /app/server && \
npm install --no-save libsql && \
npm cache clean --force
# cli
COPY --from=build /app/cli/cli.sh /usr/local/bin/cli
RUN chmod +x /usr/local/bin/cli
# Install Linux packages
RUN apk add --no-cache \
+1
View File
@@ -36,3 +36,4 @@ RUN pnpm install
# Copy Project
COPY src ./
ENTRYPOINT [ "pnpm", "run" ]
+10 -12
View File
@@ -7,12 +7,6 @@
[![GitHub Release](https://img.shields.io/github/v/release/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/releases/latest)
[![Image Pulls](https://img.shields.io/badge/image_pulls-12M+-blue)](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!
<!-- TOOD: update screenshot -->
@@ -43,11 +37,6 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
> [!NOTE]
> To better manage documentation for this project, it has its own site here: [https://wg-easy.github.io/wg-easy/latest](https://wg-easy.github.io/wg-easy/latest)
<!-- TODO: remove after release -->
> [!WARNING]
> As the Docs are still in Pre-release, you can access them here [https://wg-easy.github.io/wg-easy/Pre-release](https://wg-easy.github.io/wg-easy/Pre-release)
- [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/)
- [Basic Installation](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/)
- [Caddy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/caddy/)
@@ -81,7 +70,7 @@ The easiest way to run WireGuard Easy is with Docker Compose.
Just download [`docker-compose.yml`](docker-compose.yml) and execute `sudo docker compose up -d`.
Now setup a reverse proxy to be able to access the Web UI from the internet.
Now setup a reverse proxy to be able to access the Web UI securely from the internet.
If you want to access the Web UI over HTTP, change the env var `INSECURE` to `true`. This is not recommended. Only use this for testing
@@ -116,6 +105,15 @@ If you add something that should be auto-importable and VSCode complains, run:
```shell
cd src
pnpm install
cd ..
```
### Test Cli
This starts the cli with docker
```shell
pnpm cli:dev
```
## License
+44
View 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 -1
View File
@@ -2,7 +2,7 @@ services:
wg-easy:
build:
dockerfile: ./Dockerfile.dev
command: pnpm run dev
command: dev
volumes:
- ./src/:/app/
- temp:/app/.nuxt/
+26 -25
View File
@@ -3,21 +3,35 @@ volumes:
services:
wg-easy:
#environment:
# Optional:
# - PORT=51821
# - HOST=0.0.0.0
# - INSECURE=false
environment:
# Change Language:
# (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi)
- LANG=de
# ⚠️ Required:
# Change this to your host's public address
- WG_HOST=raspberrypi.local
image: ghcr.io/wg-easy/wg-easy:15
# Optional:
# - PASSWORD_HASH=$$2y$$10$$hBCoykrB95WSzuV4fafBzOHWKu9sbyVa34GJr8VV5R/pIelfEMYyG # (needs double $$, hash of 'foobar123'; see "How_to_generate_an_bcrypt_hash.md" for generate the hash)
# - PORT=51821
# - WG_PORT=51820
# - WG_CONFIG_PORT=92820
# - WG_DEFAULT_ADDRESS=10.8.0.x
# - WG_DEFAULT_DNS=1.1.1.1
# - WG_MTU=1420
# - WG_ALLOWED_IPS=192.168.15.0/24, 10.0.1.0/24
# - WG_PERSISTENT_KEEPALIVE=25
# - WG_PRE_UP=echo "Pre Up" > /etc/wireguard/pre-up.txt
# - WG_POST_UP=echo "Post Up" > /etc/wireguard/post-up.txt
# - WG_PRE_DOWN=echo "Pre Down" > /etc/wireguard/pre-down.txt
# - WG_POST_DOWN=echo "Post Down" > /etc/wireguard/post-down.txt
# - UI_TRAFFIC_STATS=true
# - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart)
image: ghcr.io/wg-easy/wg-easy:14
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"
@@ -25,20 +39,7 @@ services:
cap_add:
- NET_ADMIN
- SYS_MODULE
# - NET_RAW # ⚠️ Uncomment if using Podman Compose
# - NET_RAW # ⚠️ Uncomment if using Podman
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64
+5
View File
@@ -0,0 +1,5 @@
{
"tabWidth": 4,
"semi": true,
"singleQuote": true
}
+5
View File
@@ -2,6 +2,11 @@
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.
+9 -9
View File
@@ -16,15 +16,15 @@ You need to add a scrape config to your Prometheus configuration file. Here is a
```yaml
scrape_configs:
- job_name: "wg-easy"
scrape_interval: 30s
metrics_path: /metrics/prometheus
static_configs:
- targets:
- "localhost:51821"
authorization:
type: Bearer
credentials: "SuperSecurePassword"
- job_name: 'wg-easy'
scrape_interval: 30s
metrics_path: /metrics/prometheus
static_configs:
- targets:
- 'localhost:51821'
authorization:
type: Bearer
credentials: 'SuperSecurePassword'
```
## Grafana Dashboard
@@ -7,7 +7,7 @@ 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 use armv6, you unfortunately won't be able to migrate to `v15`.
- If you are connecting to the web ui via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container.
## Migration
@@ -42,7 +42,7 @@ docker-compose down
Follow the instructions in the [Getting Started][docs-getting-started] or [Basic Installation][docs-examples] guide to start the new container.
In the setup wizard, select that you already already have a configuration file and upload the `wg0.json` file you downloaded in the backup step.
In the setup wizard, select that you already have a configuration file and upload the `wg0.json` file you downloaded in the backup step.
[docs-getting-started]: ../../getting-started.md
[docs-examples]: ../../examples/tutorials/basic-installation.md
+1 -1
View File
@@ -12,7 +12,7 @@ When refactoring, writing or altering files, adhere to these rules:
## Documentation
Make sure to select `nightly` in the dropdown menu at the top. Navigate to the page you would like to edit and click the edit button in the top right. This allows you to make changes and create a pull-request.
Make sure to select `edge` in the dropdown menu at the top. Navigate to the page you would like to edit and click the edit button in the top right. This allows you to make changes and create a pull-request.
Alternatively you can make the changes locally. For that you'll need to have Docker installed. Run
@@ -50,7 +50,7 @@ The development workflow is the following:
3. Document your improvements if necessary
4. [Commit][commit] (and [sign your commit][gpg]), push and create a pull-request to merge into `master`. Please **use the pull-request template** to provide a minimum of contextual information and make sure to meet the requirements of the checklist.
Pull requests are automatically tested against the CI and will be reviewed when tests pass. When your changes are validated, your branch is merged. CI builds the new `:nightly` image every night and your changes will be includes in the next version release.
Pull requests are automatically tested against the CI and will be reviewed when tests pass. When your changes are validated, your branch is merged. CI builds the new `:edge` image on every push to the `master` branch and your changes will be included in the next version release.
[docs-latest]: https://wg-easy.github.io/wg-easy/latest
[github-file-readme]: https://github.com/wg-easy/wg-easy/blob/master/README.md
@@ -19,13 +19,13 @@ 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
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`
@@ -8,7 +8,7 @@ title: Basic Installation
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)
3. You need a supported architecture (x86_64, arm64, armv7)
4. You need curl installed on your host
## Install Docker
@@ -19,22 +19,22 @@ Follow the Docs here: <https://docs.docker.com/engine/install/> and install Dock
1. Create a directory for the configuration files (you can choose any directory you like):
```shell
sudo mkdir -p /etc/docker/containers/wg-easy
```
```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
```
```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
```
```shell
cd /etc/docker/containers/wg-easy
sudo docker-compose up -d
```
## Setup Firewall
@@ -48,6 +48,7 @@ These ports can be changed, so if you change them you have to update your firewa
- To setup traefik follow the instructions here: [Traefik](./traefik.md)
- To setup 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`
@@ -87,15 +87,15 @@ In the Admin Panel of your WireGuard server, go to the `Hooks` tab and add the f
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;
```
```shell
nft add table inet wg_table; nft add chain inet wg_table prerouting { type nat hook prerouting priority 100 \; }; nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \; }; nft add rule inet wg_table postrouting ip saddr {{ipv4Cidr}} oifname {{device}} masquerade; nft add rule inet wg_table postrouting ip6 saddr {{ipv6Cidr}} oifname {{device}} masquerade; nft add chain inet wg_table input { type filter hook input priority 0 \; policy accept \; }; nft add rule inet wg_table input udp dport {{port}} accept; nft add rule inet wg_table input tcp dport {{uiPort}} accept; nft add chain inet wg_table forward { type filter hook forward priority 0 \; policy accept \; }; nft add rule inet wg_table forward iifname "wg0" accept; nft add rule inet wg_table forward oifname "wg0" accept;
```
2. PostDown
```shell
nft delete table inet wg_table
```
```shell
nft delete table inet wg_table
```
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.
@@ -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.
+73 -73
View File
@@ -20,25 +20,25 @@ 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
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
traefik:
external: true
```
## Create traefik.yml
@@ -47,47 +47,47 @@ File: `/etc/docker/volumes/traefik/traefik.yml`
```yaml
log:
level: INFO
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: {}
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
dashboard: true
certificatesResolvers:
letsencrypt:
acme:
email: $mail@example.com$
storage: acme.json
httpChallenge:
entryPoint: web
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
docker:
watch: true
network: traefik
exposedByDefault: false
file:
filename: traefik_dynamic.yml
serversTransport:
insecureSkipVerify: true
insecureSkipVerify: true
```
## Create traefik_dynamic.yml
@@ -96,33 +96,33 @@ 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
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
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
+30 -30
View File
@@ -1,7 +1,7 @@
---
title: FAQ
hide:
- navigation
- 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.
@@ -14,15 +14,15 @@ 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:
```bash
```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:
```bash
echo "wireguard" | sudo tee -a /etc/modules
```
```shell
echo "wireguard" | sudo tee -a /etc/modules
```
## can't initialize iptables table `nat': Table does not exist (do you need to insmod?)
@@ -32,15 +32,15 @@ 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:
```bash
sudo modprobe iptable_nat
```
```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:
```bash
echo "iptable_nat" | sudo tee -a /etc/modules
```
```shell
echo "iptable_nat" | sudo tee -a /etc/modules
```
## can't initialize ip6tables table `nat': Table does not exist (do you need to insmod?)
@@ -50,15 +50,15 @@ 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:
```bash
sudo modprobe ip6table_nat
```
```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:
```bash
echo "ip6table_nat" | sudo tee -a /etc/modules
```
```shell
echo "ip6table_nat" | sudo tee -a /etc/modules
```
## can't initialize iptables table `filter': Permission denied
@@ -68,15 +68,15 @@ 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:
```bash
sudo modprobe iptable_filter
```
```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:
```bash
echo "iptable_filter" | sudo tee -a /etc/modules
```
```shell
echo "iptable_filter" | sudo tee -a /etc/modules
```
## can't initialize ip6tables table `filter': Permission denied
@@ -86,12 +86,12 @@ To resolve this issue, you can try the following steps:
1. **Load the `filter` kernel module**: If the `filter` kernel module is not loaded, you can load it manually by running:
```bash
sudo modprobe ip6table_filter
```
```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:
```bash
echo "ip6table_filter" | sudo tee -a /etc/modules
```
```shell
echo "ip6table_filter" | sudo tee -a /etc/modules
```
+17 -41
View File
@@ -1,7 +1,7 @@
---
title: Getting Started
hide:
- navigation
- navigation
---
This page explains how to get started with `wg-easy`. The guide uses Docker Compose as a reference. In our examples, we mount the named volume `etc_wireguard` to `/etc/wireguard` inside the container.
@@ -12,7 +12,7 @@ Before you can get started with deploying your own VPN, there are some requireme
1. You need to have a host that you can manage
2. You need to have a domain name or a public IP address
3. You need a supported architecture (x86_64, arm64)
3. You need a supported architecture (x86_64, arm64, armv7)
### Host Setup
@@ -41,14 +41,16 @@ To understand which tags you should use, read this section carefully. [Our CI][g
All workflows are using the tagging convention listed below. It is subsequently applied to all images.
| 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 |
| `latest` | latest tag | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | stable as possible get bug fixes quickly when needed, see Releases for more information. |
| `15.0` | latest patch for that minor tag | `ghcr.io/wg-easy/wg-easy:15.0` | latest patches for specific minor version |
| `15.0.0` | specific tag | `ghcr.io/wg-easy/wg-easy:15.0.0` | specific release, don't use this as this will not get updated |
| `nightly` | [`master`](https://github.com/wg-easy/wg-easy/tree/master) | `ghcr.io/wg-easy/wg-easy:nightly` | mostly unstable gets frequent package and code updates, deployed against [`master`](https://github.com/wg-easy/wg-easy/tree/master). |
| `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs before landing into [`master`](https://github.com/wg-easy/wg-easy/tree/master). |
| 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`).
@@ -56,41 +58,15 @@ When publishing a tag we follow the [Semantic Versioning][semver] specification.
[ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy
[semver]: https://semver.org/
### Get All Files
### Follow tutorials
Issue the following command to acquire the necessary file:
- [Basic Installation with Docker Compose (Recommended)](./examples/tutorials/basic-installation.md)
- [Simple Installation with Docker Run](./examples/tutorials/docker-run.md)
- [Advanced Installation with Podman](./examples/tutorials/podman-nft.md)
```shell
wget "https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml"
```
### Start the Container
To start the container, issue the following command:
```shell
sudo docker compose up -d
```
### Configuration Steps
Now follow the setup process in your web browser
### Stopping the Container
To stop the container, issue the following command:
```shell
sudo docker compose down
```
/// danger | Using the Correct Commands For Stopping and Starting `wg-easy`
/// danger | Use the Correct Commands For Stopping and Starting `wg-easy`
**Use `sudo docker compose up / down`, not `sudo docker compose start / stop`**. Otherwise, the container is not properly destroyed and you may experience problems during startup because of inconsistent state.
///
**That's it! It really is that easy**.
If you need more help you can read the [Basic Installation Tutorial][basic-installation].
[basic-installation]: ./examples/tutorials/basic-installation.md
+22 -1
View File
@@ -2,4 +2,25 @@
title: 2FA
---
TODO
The user can enable 2FA from the Account page. The Account page is accessible from the dropdown menu in the top right corner of the application.
## Enable TOTP
- **Enable Two Factor Authentication**: Enable TOTP for the user.
## Configure TOTP
A QR code will be displayed. Scan the QR code with your TOTP application (e.g., Google Authenticator, Authy, etc.) to add the account.
To verify that the TOTP key is working, the user must enter the TOTP code generated by the TOTP application.
- **TOTP Key**: The TOTP key for the user. This key is used to generate the TOTP code.
- **TOTP Code**: The current TOTP code for the user. This code is used to verify the TOTP key.
- **Enable Two Factor Authentication**: Enable TOTP for the user.
## Disable TOTP
To disable TOTP, the user must enter the current password.
- **Current Password**: The current password of the user.
- **Disable Two Factor Authentication**: Disable TOTP for the user.
-5
View File
@@ -1,5 +0,0 @@
---
title: Edit Account
---
TODO
+43
View 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.
+46 -1
View File
@@ -2,4 +2,49 @@
title: Edit Client
---
TODO
## General
- **Name**: The name of the client.
- **Enabled**: Whether the client can connect to the VPN.
- **Expire Date**: The date the client will be disabled.
## Address
- **IPv4**: The IPv4 address of the client.
- **IPv6**: The IPv6 address of the client.
## Allowed IPs
Which IPs will be routed through the VPN.
This will not prevent the user from modifying it locally and accessing IP ranges that they should not be able to access.
Use firewall rules to prevent access to IP ranges that the user should not be able to access.
## Server Allowed IPs
Which IPs will be routed to the client.
## DNS
The DNS server that the client will use.
## Advanced
- **MTU**: The maximum transmission unit for the client.
- **Persistent Keepalive**: The interval for sending keepalive packets to the server.
## Hooks
This can only be used for clients that use `wg-quick`. Setting this will throw a error when importing the config on other clients.
- **PreUp**: Commands to run before the interface is brought up.
- **PostUp**: Commands to run after the interface is brought up.
- **PreDown**: Commands to run before the interface is brought down.
- **PostDown**: Commands to run after the interface is brought down.
## Actions
- **Save**: Save the changes made in the form.
- **Revert**: Revert the changes made in the form.
- **Delete**: Delete the client.
-5
View File
@@ -1,5 +0,0 @@
---
title: Login
---
TODO
+20 -1
View File
@@ -2,4 +2,23 @@
title: Setup
---
TODO
## User Setup
- **Username**: The username of the user.
- **Password**: The password of the user.
- **Confirm Password**: The password of the user.
## Existing Setup
If you have the config from the previous version, you can import it by clicking "Yes". This currently expects a config from v14.
If this is the first time you are using this, you can click "No" to create a new config.
### No - Host Setup
- **Host**: The host of the server. The clients will connect to this address. This can be a domain name or an IP address. Make sure to wrap it in brackets if it is an IPv6 address. For example: `[::1]` or `[2001:db8::1]`.
- **Port**: The port of the server. The clients will connect to this port. The server will listen on this port.
### Yes - Migration
Select the `wg0.json` file from the previous version. Read [Migrate from v14 to v15](../advanced/migrate/from-14-to-15.md) for more information.
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: Home
hide:
- navigation
- navigation
---
# Welcome to the Documentation for `wg-easy`
+73 -73
View File
@@ -1,87 +1,87 @@
site_name: "wg-easy"
site_description: "The easiest way to run WireGuard VPN + Web-based Admin UI."
site_author: "WireGuard Easy"
site_name: 'wg-easy'
site_description: 'The easiest way to run WireGuard VPN + Web-based Admin UI.'
site_author: 'WireGuard Easy'
copyright: >
<p>
&copy <a href="https://github.com/wg-easy"><em>Wireguard Easy</em></a><br/>
<span>This project is licensed under AGPL-3.0-only.</span><br/>
<span>This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security</span><br/>
<span>"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld</span>
</p>
<p>
&copy <a href="https://github.com/wg-easy"><em>Wireguard Easy</em></a><br/>
<span>This project is licensed under AGPL-3.0-only.</span><br/>
<span>This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security</span><br/>
<span>"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld</span>
</p>
repo_url: https://github.com/wg-easy/wg-easy
repo_name: wg-easy
edit_uri: "edit/master/docs/content"
edit_uri: 'edit/master/docs/content'
docs_dir: "content/"
docs_dir: 'content/'
site_url: https://wg-easy.github.io/wg-easy
theme:
name: material
favicon: assets/logo/favicon.png
logo: assets/logo/logo.png
icon:
repo: fontawesome/brands/github
features:
- navigation.tabs
- navigation.top
- navigation.expand
- navigation.instant
- content.action.edit
- content.action.view
- content.code.annotate
palette:
# Light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: grey
accent: red
toggle:
icon: material/weather-night
name: Switch to dark mode
# Dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: grey
accent: red
toggle:
icon: material/weather-sunny
name: Switch to light mode
name: material
favicon: assets/logo/favicon.png
logo: assets/logo/logo.png
icon:
repo: fontawesome/brands/github
features:
- navigation.tabs
- navigation.top
- navigation.expand
- navigation.instant
- content.action.edit
- content.action.view
- content.code.annotate
palette:
# Light mode
- media: '(prefers-color-scheme: light)'
scheme: default
primary: grey
accent: red
toggle:
icon: material/weather-night
name: Switch to dark mode
# Dark mode
- media: '(prefers-color-scheme: dark)'
scheme: slate
primary: grey
accent: red
toggle:
icon: material/weather-sunny
name: Switch to light mode
extra:
version:
provider: mike
version:
provider: mike
markdown_extensions:
- toc:
anchorlink: true
- abbr
- attr_list
- pymdownx.blocks.admonition:
types:
- danger
- note
- info
- question
- warning
- pymdownx.details
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
slugify: !!python/object/apply:pymdownx.slugs.slugify
kwds:
case: lower
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.magiclink
- pymdownx.inlinehilite
- pymdownx.tilde
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- toc:
anchorlink: true
- abbr
- attr_list
- pymdownx.blocks.admonition:
types:
- danger
- note
- info
- question
- warning
- pymdownx.details
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
slugify: !!python/object/apply:pymdownx.slugs.slugify
kwds:
case: lower
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.magiclink
- pymdownx.inlinehilite
- pymdownx.tilde
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
+8 -3
View File
@@ -2,10 +2,15 @@
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "docker compose -f docker-compose.dev.yml up --build",
"dev": "docker compose -f docker-compose.dev.yml up wg-easy --build",
"cli:dev": "docker compose -f docker-compose.dev.yml run --build --rm -it wg-easy cli:dev",
"build": "docker build -t wg-easy .",
"docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8080",
"scripts:version": "bash scripts/version.sh"
"scripts:version": "bash scripts/version.sh",
"format:check:docs": "prettier --check docs"
},
"packageManager": "pnpm@10.8.0"
"devDependencies": {
"prettier": "^3.5.3"
},
"packageManager": "pnpm@10.11.0"
}
+16 -1
View File
@@ -6,4 +6,19 @@ settings:
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: {}
-1
View File
@@ -1 +0,0 @@
public-hoist-pattern[]=@libsql/linux*
+3 -3
View File
@@ -10,12 +10,12 @@
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('change', ipv4Cidr, ipv6Cidr)">
<BasePrimaryButton @click="$emit('change', ipv4Cidr, ipv6Cidr)">
{{ $t('dialog.change') }}
</BaseButton>
</BasePrimaryButton>
</DialogClose>
</template>
</BaseDialog>
@@ -7,12 +7,12 @@
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('restart')">
<BasePrimaryButton @click="$emit('restart')">
{{ $t('admin.interface.restart') }}
</BaseButton>
</BasePrimaryButton>
</DialogClose>
</template>
</BaseDialog>
+3 -3
View File
@@ -13,12 +13,12 @@
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('change', selected)">
<BasePrimaryButton @click="$emit('change', selected)">
{{ $t('dialog.change') }}
</BaseButton>
</BasePrimaryButton>
</DialogClose>
</template>
</BaseDialog>
+26
View File
@@ -0,0 +1,26 @@
<template>
<component
:is="elementType"
role="button"
class="inline-flex items-center rounded border-2 border-red-800 bg-red-800 px-4 py-2 text-white transition hover:border-red-600 hover:bg-red-600"
v-bind="attrs"
>
<slot />
</component>
</template>
<script setup lang="ts">
const props = defineProps({
as: {
type: String,
default: 'button',
},
});
const elementType = computed(() => props.as);
const attrs = computed(() => {
const { as, ...attrs } = props;
return attrs;
});
</script>
@@ -20,7 +20,7 @@ const props = defineProps({
const elementType = computed(() => props.as);
const attrs = computed(() => {
const { as, ...attrs } = props;
return attrs;
const { as, ...rest } = props;
return rest;
});
</script>
+4 -2
View File
@@ -18,10 +18,12 @@
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="createClient">{{ $t('client.create') }}</BaseButton>
<BasePrimaryButton @click="createClient">
{{ $t('client.create') }}
</BasePrimaryButton>
</DialogClose>
</template>
</BaseDialog>
+4 -4
View File
@@ -9,12 +9,12 @@
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
<BasePrimaryButton>{{ $t('dialog.cancel') }}</BasePrimaryButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('delete')">{{
$t('client.deleteClient')
}}</BaseButton>
<BaseSecondaryButton @click="$emit('delete')">
{{ $t('client.deleteClient') }}
</BaseSecondaryButton>
</DialogClose>
</template>
</BaseDialog>
+2 -2
View File
@@ -2,10 +2,10 @@
<p class="m-10 text-center text-sm text-gray-400 dark:text-neutral-400">
{{ $t('client.empty') }}<br /><br />
<ClientsCreateDialog>
<BaseButton as="span">
<BaseSecondaryButton as="span">
<IconsPlus class="w-4 md:mr-2" />
<span class="text-sm">{{ $t('client.new') }}</span>
</BaseButton>
</BaseSecondaryButton>
</ClientsCreateDialog>
</p>
</template>
+2 -2
View File
@@ -1,8 +1,8 @@
<template>
<ClientsCreateDialog>
<BaseButton as="span">
<BaseSecondaryButton as="span">
<IconsPlus class="w-4 md:mr-2" />
<span class="text-sm max-md:hidden">{{ $t('client.newShort') }}</span>
</BaseButton>
</BaseSecondaryButton>
</ClientsCreateDialog>
</template>
+1 -1
View File
@@ -10,7 +10,7 @@
</template>
<template #actions>
<DialogClose>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
</DialogClose>
</template>
</BaseDialog>
+2 -2
View File
@@ -1,12 +1,12 @@
<template>
<BaseButton @click="toggleSort">
<BasePrimaryButton @click="toggleSort">
<IconsArrowDown
v-if="globalStore.sortClient === true"
class="w-4 md:mr-2"
/>
<IconsArrowUp v-else class="w-4 md:mr-2" />
<span class="text-sm max-md:hidden"> {{ $t('client.sort') }}</span>
</BaseButton>
</BasePrimaryButton>
</template>
<script setup lang="ts">
+2 -2
View File
@@ -12,7 +12,7 @@
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
@input="update($event, i)"
/>
<BaseButton
<BaseSecondaryButton
as="input"
type="button"
class="rounded-lg"
@@ -22,7 +22,7 @@
</div>
</div>
<div class="mt-2">
<BaseButton
<BasePrimaryButton
as="input"
type="button"
class="rounded-lg"
+3 -3
View File
@@ -7,7 +7,7 @@
<IconsInfo class="size-4" />
</BaseTooltip>
</div>
<div class="flex">
<div class="flex gap-1">
<BaseInput
:id="id"
v-model.trim="data"
@@ -18,12 +18,12 @@
/>
<ClientOnly>
<AdminSuggestDialog :url="url" @change="data = $event">
<BaseButton as="span">
<BasePrimaryButton as="span">
<div class="flex items-center gap-3">
<IconsSparkles class="w-4" />
<span>{{ $t('admin.config.suggest') }}</span>
</div>
</BaseButton>
</BasePrimaryButton>
</AdminSuggestDialog>
</ClientOnly>
</div>
+2 -2
View File
@@ -12,7 +12,7 @@
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
@input="update($event, i)"
/>
<BaseButton
<BaseSecondaryButton
as="input"
type="button"
class="rounded-lg"
@@ -22,7 +22,7 @@
</div>
</div>
<div class="mt-2">
<BaseButton
<BasePrimaryButton
as="input"
type="button"
class="rounded-lg"
@@ -0,0 +1,16 @@
<template>
<input
:value="label"
:type="type ?? 'button'"
class="col-span-2 rounded-lg border-2 border-red-800 bg-red-800 py-2 text-white hover:border-red-600 hover:bg-red-600 focus:border-red-800 focus:outline-0 focus:ring-0"
/>
</template>
<script lang="ts" setup>
import type { InputTypeHTMLAttribute } from 'vue';
defineProps<{
label: string;
type?: InputTypeHTMLAttribute;
}>();
</script>
+2 -2
View File
@@ -15,12 +15,12 @@
:to="`/admin/${item.id}`"
active-class="bg-red-800 rounded"
>
<BaseButton
<BaseSecondaryButton
as="span"
class="w-full cursor-pointer rounded p-2 font-medium transition-colors duration-200 hover:bg-red-800 dark:text-neutral-200"
>
{{ item.name }}
</BaseButton>
</BaseSecondaryButton>
</NuxtLink>
</div>
</div>
+2 -2
View File
@@ -49,8 +49,8 @@
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('form.actions') }}</FormHeading>
<FormActionField type="submit" :label="$t('form.save')" />
<FormActionField :label="$t('form.revert')" @click="revert" />
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
<FormSecondaryActionField :label="$t('form.revert')" @click="revert" />
</FormGroup>
</FormElement>
</main>
+2 -2
View File
@@ -32,8 +32,8 @@
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('form.actions') }}</FormHeading>
<FormActionField type="submit" :label="$t('form.save')" />
<FormActionField :label="$t('form.revert')" @click="revert" />
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
<FormSecondaryActionField :label="$t('form.revert')" @click="revert" />
</FormGroup>
</FormElement>
</main>
+2 -2
View File
@@ -25,8 +25,8 @@
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('form.actions') }}</FormHeading>
<FormActionField type="submit" :label="$t('form.save')" />
<FormActionField :label="$t('form.revert')" @click="revert" />
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
<FormSecondaryActionField :label="$t('form.revert')" @click="revert" />
</FormGroup>
</FormElement>
</main>
+4 -4
View File
@@ -23,15 +23,15 @@
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('form.actions') }}</FormHeading>
<FormActionField type="submit" :label="$t('form.save')" />
<FormActionField :label="$t('form.revert')" @click="revert" />
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
<FormSecondaryActionField :label="$t('form.revert')" @click="revert" />
<AdminCidrDialog
trigger-class="col-span-2"
:ipv4-cidr="data.ipv4Cidr"
:ipv6-cidr="data.ipv6Cidr"
@change="changeCidr"
>
<FormActionField
<FormSecondaryActionField
:label="$t('admin.interface.changeCidr')"
class="w-full"
tabindex="-1"
@@ -41,7 +41,7 @@
trigger-class="col-span-2"
@restart="restartInterface"
>
<FormActionField
<FormSecondaryActionField
:label="$t('admin.interface.restart')"
class="w-full"
tabindex="-1"
+6 -3
View File
@@ -107,14 +107,17 @@
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('form.actions') }}</FormHeading>
<FormActionField type="submit" :label="$t('form.save')" />
<FormActionField :label="$t('form.revert')" @click="revert" />
<FormPrimaryActionField type="submit" :label="$t('form.save')" />
<FormSecondaryActionField
:label="$t('form.revert')"
@click="revert"
/>
<ClientsDeleteDialog
trigger-class="col-span-2"
:client-name="data.name"
@delete="deleteClient"
>
<FormActionField
<FormSecondaryActionField
label="Delete"
class="w-full"
type="button"
+8 -5
View File
@@ -18,7 +18,7 @@
v-model="email"
:label="$t('user.email')"
/>
<FormActionField type="submit" :label="$t('form.save')" />
<FormSecondaryActionField type="submit" :label="$t('form.save')" />
</FormGroup>
</FormElement>
<FormElement @submit.prevent="updatePassword">
@@ -42,7 +42,7 @@
autocomplete="new-password"
:label="$t('general.confirmPassword')"
/>
<FormActionField
<FormSecondaryActionField
type="submit"
:label="$t('general.updatePassword')"
/>
@@ -55,7 +55,10 @@
v-if="!authStore.userData?.totpVerified && !twofa"
class="col-span-2 flex flex-col"
>
<FormActionField :label="$t('me.enable2fa')" @click="setup2fa" />
<FormSecondaryActionField
:label="$t('me.enable2fa')"
@click="setup2fa"
/>
</div>
<div
v-if="!authStore.userData?.totpVerified && twofa"
@@ -81,7 +84,7 @@
v-model="code"
:label="$t('general.2faCode')"
/>
<FormActionField
<FormSecondaryActionField
:label="$t('me.enable2fa')"
@click="enable2fa"
/>
@@ -101,7 +104,7 @@
type="password"
autocomplete="current-password"
/>
<FormActionField
<FormSecondaryActionField
:label="$t('me.disable2fa')"
@click="disable2fa"
/>
+3 -1
View File
@@ -4,7 +4,9 @@
{{ $t('setup.welcomeDesc') }}
</p>
<NuxtLink to="/setup/2" class="mt-8">
<BaseButton as="span">{{ $t('general.continue') }}</BaseButton>
<BasePrimaryButton as="span">
{{ $t('general.continue') }}
</BasePrimaryButton>
</NuxtLink>
</div>
</template>
+3 -1
View File
@@ -29,7 +29,9 @@
/>
</div>
<div class="mt-4 flex justify-center">
<BaseButton @click="submit">{{ $t('setup.createAccount') }}</BaseButton>
<BasePrimaryButton @click="submit">
{{ $t('setup.createAccount') }}
</BasePrimaryButton>
</div>
</div>
</div>
+4 -4
View File
@@ -5,14 +5,14 @@
</p>
<div class="mt-4 flex justify-center gap-3">
<NuxtLink to="/setup/4" class="w-20">
<BaseButton as="span" class="w-full justify-center">
<BasePrimaryButton as="span" class="w-full justify-center">
{{ $t('general.no') }}
</BaseButton>
</BasePrimaryButton>
</NuxtLink>
<NuxtLink to="/setup/migrate" class="w-20">
<BaseButton as="span" class="w-full justify-center">
<BaseSecondaryButton as="span" class="w-full justify-center">
{{ $t('general.yes') }}
</BaseButton>
</BaseSecondaryButton>
</NuxtLink>
</div>
</div>
+3 -1
View File
@@ -23,7 +23,9 @@
/>
</div>
<div class="mt-4 flex justify-center">
<BaseButton @click="submit">{{ $t('general.continue') }}</BaseButton>
<BasePrimaryButton @click="submit">
{{ $t('general.continue') }}
</BasePrimaryButton>
</div>
</div>
</div>
+3 -1
View File
@@ -8,7 +8,9 @@
<input id="migration" type="file" @change="onChangeFile" />
</div>
<div class="mt-4">
<BaseButton @click="submit">{{ $t('setup.upload') }}</BaseButton>
<BasePrimaryButton @click="submit">
{{ $t('setup.upload') }}
</BasePrimaryButton>
</div>
</div>
</template>
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="flex flex-col items-center">
<p>{{ $t('setup.successful') }}</p>
<NuxtLink to="/login" class="mt-4">
<BaseButton as="span">{{ $t('login.signIn') }}</BaseButton>
<BasePrimaryButton as="span">{{ $t('login.signIn') }}</BasePrimaryButton>
</NuxtLink>
</div>
</template>
+25
View File
@@ -0,0 +1,25 @@
// @ts-check
import { fileURLToPath } from 'node:url';
import esbuild from 'esbuild';
esbuild.build({
entryPoints: [fileURLToPath(new URL('./index.ts', import.meta.url))],
bundle: true,
outfile: fileURLToPath(new URL('../.output/server/cli.mjs', import.meta.url)),
platform: 'node',
format: 'esm',
plugins: [
{
name: 'make-all-packages-external',
setup(build) {
let filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../"
build.onResolve({ filter }, (args) => ({
path: args.path,
external: true,
}));
},
},
],
logLevel: 'info',
});
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
set -e
node /app/server/cli.mjs "$@"
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env node
// ! Auto Imports are not supported in this file
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import { defineCommand, runMain } from 'citty';
import { consola } from 'consola';
import { eq } from 'drizzle-orm';
import packageJson from '../package.json';
import * as schema from '../server/database/schema';
import { hashPassword } from '../server/utils/password';
const client = createClient({ url: 'file:/etc/wireguard/wg-easy.db' });
const db = drizzle({ client, schema });
const dbAdminReset = defineCommand({
meta: {
name: 'db:admin:reset',
description: 'Reset the admin user',
},
args: {
password: {
type: 'string',
description: 'New password for the admin user',
required: false,
},
},
async run(ctx) {
let password = ctx.args.password || undefined;
if (!password) {
password = await consola.prompt('Please enter a new password:', {
type: 'text',
});
}
if (!password) {
consola.error('Password is required');
return;
}
if (password.length < 12) {
consola.error('Password must be at least 12 characters long');
return;
}
console.info('Setting new password for admin user...');
const hash = await hashPassword(password);
const user = await db.transaction(async (tx) => {
const user = await tx
.select()
.from(schema.user)
.where(eq(schema.user.id, 1))
.get();
if (!user) {
consola.error('Admin user not found');
return;
}
await tx
.update(schema.user)
.set({
password: hash,
})
.where(eq(schema.user.id, 1));
return user;
});
if (!user) {
consola.error('Failed to update admin user');
return;
}
consola.success(
`Successfully updated admin user ${user.id} (${user.username})`
);
},
});
const main = defineCommand({
meta: {
name: 'wg-easy',
version: packageJson.version,
description: 'Command Line Interface',
},
subCommands: {
'db:admin:reset': dbAdminReset,
},
});
runMain(main);
+6
View File
@@ -41,6 +41,9 @@ export default defineNuxtConfig({
detectBrowserLanguage: {
useCookie: true,
},
bundle: {
optimizeTranslationDirective: false,
},
},
nitro: {
esbuild: {
@@ -52,6 +55,9 @@ export default defineNuxtConfig({
alias: {
'#db': fileURLToPath(new URL('./server/database/', import.meta.url)),
},
externals: {
traceInclude: [fileURLToPath(new URL('./cli/index.ts', import.meta.url))],
},
},
alias: {
// for typecheck reasons (https://github.com/nuxt/cli/issues/323)
+32 -23
View File
@@ -1,11 +1,11 @@
{
"name": "wg-easy",
"version": "15.0.0-beta.12",
"version": "15.0.0",
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"build": "nuxt build && pnpm cli:build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
@@ -15,53 +15,62 @@
"format:check": "prettier . --check",
"typecheck": "nuxt typecheck",
"check:all": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm build",
"db:generate": "drizzle-kit generate"
"db:generate": "drizzle-kit generate",
"cli:build": "node cli/build.js",
"cli:dev": "tsx cli/index.ts"
},
"dependencies": {
"@eschricht/nuxt-color-mode": "^1.1.5",
"@heroicons/vue": "^2.2.0",
"@libsql/client": "^0.15.3",
"@nuxtjs/i18n": "^9.5.3",
"@nuxtjs/tailwindcss": "^6.13.2",
"@libsql/client": "^0.15.7",
"@nuxtjs/i18n": "^9.5.4",
"@nuxtjs/tailwindcss": "^6.14.0",
"@phc/format": "^1.0.0",
"@pinia/nuxt": "^0.11.0",
"@tailwindcss/forms": "^0.5.10",
"apexcharts": "^4.5.0",
"argon2": "^0.41.1",
"basic-auth": "^2.0.1",
"apexcharts": "^4.7.0",
"argon2": "^0.43.0",
"cidr-tools": "^11.0.3",
"citty": "^0.1.6",
"consola": "^3.4.2",
"crc-32": "^1.2.2",
"debug": "^4.4.0",
"drizzle-orm": "^0.41.0",
"debug": "^4.4.1",
"drizzle-orm": "^0.43.1",
"ip-bigint": "^8.2.1",
"is-cidr": "^5.1.1",
"is-ip": "^5.0.1",
"js-sha256": "^0.11.0",
"lowdb": "^7.0.1",
"nuxt": "^3.16.2",
"js-sha256": "^0.11.1",
"nuxt": "^3.17.4",
"otpauth": "^9.4.0",
"pinia": "^3.0.2",
"qr": "^0.4.0",
"qr": "^0.4.2",
"radix-vue": "^1.9.17",
"semver": "^7.7.1",
"semver": "^7.7.2",
"tailwindcss": "^3.4.17",
"timeago.js": "^4.0.2",
"vue": "latest",
"vue3-apexcharts": "^1.8.0",
"zod": "^3.24.2"
"zod": "^3.25.30"
},
"devDependencies": {
"@nuxt/eslint": "1.3.0",
"@nuxt/eslint": "^1.4.1",
"@types/debug": "^4.1.12",
"@types/phc__format": "^1.0.1",
"@types/semver": "^7.7.0",
"drizzle-kit": "^0.30.6",
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.2",
"drizzle-kit": "^0.31.1",
"esbuild": "^0.25.5",
"eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"tsx": "^4.19.4",
"typescript": "^5.8.3",
"vue-tsc": "^2.2.8"
"vue-tsc": "^2.2.10"
},
"packageManager": "pnpm@10.8.0"
"packageManager": "pnpm@10.11.0",
"pnpm": {
"overrides": {
"oxc-parser": "^0.70.0"
}
}
}
+2806 -1953
View File
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,7 @@
CREATE TABLE `clients_table` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` integer NOT NULL,
`interface_id` text NOT NULL,
`name` text NOT NULL,
`ipv4_address` text NOT NULL,
`ipv6_address` text NOT NULL,
@@ -21,7 +22,8 @@ CREATE TABLE `clients_table` (
`enabled` integer NOT NULL,
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON UPDATE cascade ON DELETE restrict
FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON UPDATE cascade ON DELETE restrict,
FOREIGN KEY (`interface_id`) REFERENCES `interfaces_table`(`name`) ON UPDATE cascade ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `clients_table_ipv4_address_unique` ON `clients_table` (`ipv4_address`);--> statement-breakpoint
@@ -1,7 +1,7 @@
{
"version": "6",
"dialect": "sqlite",
"id": "b812341a-1ec2-49a6-8bc8-0332f5b32df4",
"id": "dae61656-1107-4729-ac83-f43d4cab3881",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"clients_table": {
@@ -21,6 +21,13 @@
"notNull": true,
"autoincrement": false
},
"interface_id": {
"name": "interface_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
@@ -197,6 +204,19 @@
],
"onDelete": "restrict",
"onUpdate": "cascade"
},
"clients_table_interface_id_interfaces_table_name_fk": {
"name": "clients_table_interface_id_interfaces_table_name_fk",
"tableFrom": "clients_table",
"tableTo": "interfaces_table",
"columnsFrom": [
"interface_id"
],
"columnsTo": [
"name"
],
"onDelete": "cascade",
"onUpdate": "cascade"
}
},
"compositePrimaryKeys": {},
@@ -1,6 +1,6 @@
{
"id": "c4c5bfb7-a66c-4e6b-a15c-232b16689dcf",
"prevId": "b812341a-1ec2-49a6-8bc8-0332f5b32df4",
"id": "78de2e52-c4a8-4900-86c5-92f34739623a",
"prevId": "dae61656-1107-4729-ac83-f43d4cab3881",
"version": "6",
"dialect": "sqlite",
"tables": {
@@ -21,6 +21,13 @@
"notNull": true,
"autoincrement": false
},
"interface_id": {
"name": "interface_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
@@ -197,6 +204,19 @@
],
"onUpdate": "cascade",
"onDelete": "restrict"
},
"clients_table_interface_id_interfaces_table_name_fk": {
"name": "clients_table_interface_id_interfaces_table_name_fk",
"tableFrom": "clients_table",
"columnsFrom": [
"interface_id"
],
"tableTo": "interfaces_table",
"columnsTo": [
"name"
],
"onUpdate": "cascade",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
@@ -5,14 +5,14 @@
{
"idx": 0,
"version": "6",
"when": 1743515334198,
"when": 1748426990392,
"tag": "0000_short_skin",
"breakpoints": true
},
{
"idx": 1,
"version": "6",
"when": 1743515338707,
"when": 1748427001203,
"tag": "0001_classy_the_stranger",
"breakpoints": true
}
@@ -1,7 +1,7 @@
import { sql, relations } from 'drizzle-orm';
import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { oneTimeLink, user } from '../../schema';
import { oneTimeLink, user, wgInterface } from '../../schema';
/** null means use value from userConfig */
@@ -13,6 +13,12 @@ export const client = sqliteTable('clients_table', {
onDelete: 'restrict',
onUpdate: 'cascade',
}),
interfaceId: text('interface_id')
.notNull()
.references(() => wgInterface.name, {
onDelete: 'cascade',
onUpdate: 'cascade',
}),
name: text().notNull(),
ipv4Address: text('ipv4_address').notNull().unique(),
ipv6Address: text('ipv6_address').notNull().unique(),
@@ -51,4 +57,8 @@ export const clientsRelations = relations(client, ({ one }) => ({
fields: [client.userId],
references: [user.id],
}),
interface: one(wgInterface, {
fields: [client.interfaceId],
references: [wgInterface.name],
}),
}));
@@ -108,6 +108,7 @@ export class ClientService {
name,
// TODO: properly assign user id
userId: 1,
interfaceId: 'wg0',
expiresAt,
privateKey,
publicKey,
@@ -171,6 +172,7 @@ export class ClientService {
.values({
name,
userId: 1,
interfaceId: 'wg0',
privateKey,
publicKey,
preSharedKey,
@@ -14,7 +14,7 @@ export type CreateClientType = Omit<
export type UpdateClientType = Omit<
CreateClientType,
'privateKey' | 'publicKey' | 'preSharedKey' | 'userId'
'privateKey' | 'publicKey' | 'preSharedKey' | 'userId' | 'interfaceId'
>;
const name = z
+1 -1
View File
@@ -132,7 +132,7 @@ class WireGuard {
const config = await this.getClientConfiguration({ clientId });
return encodeQR(config, 'svg', {
ecc: 'high',
scale: 4,
scale: 2,
encoding: 'byte',
});
}
+2
View File
@@ -1,3 +1,5 @@
// ! Auto Imports are not supported in this file
import argon2 from 'argon2';
import { deserialize } from '@phc/format';