Compare commits

..
Author SHA1 Message Date
theis.gaedigk e7c91c34f2 Merge remote-tracking branch 'upstream/master'
Edge / Build Docker (map[os:ubuntu-24.04-arm platform:linux/arm64]) (push) Canceled after 0s
Edge / Build Docker (map[os:ubuntu-latest platform:linux/amd64]) (push) Canceled after 0s
Lint / Check Docs (push) Canceled after 0s
Edge / Merge & Deploy Docker (push) Canceled after 0s
Edge / Build & Deploy Docs (push) Canceled after 0s
Lint / Lint (format:check) (push) Canceled after 0s
Lint / Lint (lint) (push) Canceled after 0s
Lint / Lint (typecheck) (push) Canceled after 0s
2026-07-24 12:00:29 +02:00
94a9967dd1 perf: avoid repeated client scans during CIDR changes (#2707)
* perf: avoid repeated client scans during CIDR changes (#2706)

* refactor: retain CIDR change guards

* perf: avoid repeated client reads during migration

---------

Co-authored-by: potatosips <potatosips@users.noreply.github.com>
2026-07-23 07:55:04 +02:00
Bernd Storath f731135564 use defineI18nLocaleDetector directly 2026-07-21 11:00:26 +02:00
Bernd Storath 3dfea018c3 fix changelog 2026-07-21 10:52:32 +02:00
Bernd Storath 2adadfb582 Bump version to 15.4.0-beta.1 2026-07-21 10:51:31 +02:00
Bernd Storath d4a088b951 update pull request template 2026-07-21 10:37:40 +02:00
Bernd Storath 1bc0529e63 update packages 2026-07-21 10:18:05 +02:00
Bernd Storath 5fed90e0f8 update packages 2026-07-21 10:05:18 +02:00
Bernd Storath de008e5542 add tsconfig to test 2026-07-21 09:46:47 +02:00
c50e99cd90 fix: skip ip6tables when IPv6 is disabled (#2701)
fix: skip ip6tables when IPv6 is disabled (#2698)

Co-authored-by: potatosips <potatosips@users.noreply.github.com>
2026-07-21 09:40:08 +02:00
0b97cf6d23 fix: escape Prometheus label values (#2702)
fix: escape Prometheus label values (#2699)

Co-authored-by: potatosips <potatosips@users.noreply.github.com>
2026-07-21 09:38:53 +02:00
52fb584bb0 perf: merge client status in linear time (#2703)
perf: merge client status in linear time (#2700)

Co-authored-by: potatosips <potatosips@users.noreply.github.com>
2026-07-21 09:38:19 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8ae33866fc build(deps): bump actions/setup-node from 6 to 7 (#2695)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 09:36:27 +02:00
Stephan PaternotteandGitHub 78b2838498 Update nl.json (#2686)
* Update nl.json

Dutch language updated re. 	
various improvements (#2671)

* Update nl.json

Missing comma fixed
2026-07-06 15:47:09 +02:00
Bernd StorathandGitHub 915e559514 fix: trace cli dependencies (#2683)
trace cli dependencies
2026-06-30 13:13:50 +02:00
Bernd Storath ee232569f0 update packages 2026-06-30 10:40:21 +02:00
Bernd Storath 11071c2ad5 add ai contribution policy
copied from gitea
2026-06-30 10:32:04 +02:00
Bernd StorathandGitHub ec301d17d7 chore: Improve schema (#2676)
* improve

* fix drizzle kit
2026-06-22 12:25:39 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
977a18801b build(deps): bump actions/checkout from 6 to 7 (#2674)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 07:47:37 +02:00
Bernd Storath 391bed2256 update esbuild target 2026-06-19 16:08:21 +02:00
Bernd StorathandGitHub 042dce50b5 chore: migrate to nuxt v4 (#2660)
* nuxt 4

* fix auto import
2026-06-19 15:55:38 +02:00
Bernd Storath e158d073c2 hoist packages instead of declaring them directly 2026-06-19 15:40:58 +02:00
Bernd Storath c7b665b61c update packages 2026-06-19 15:32:52 +02:00
Bernd StorathandGitHub 2af5cd04b4 chore: disable auto imports (#2672)
* disable auto imports

* fix imports

* improve cli imports

* fix imports

* fix import cycle

* fix imports
2026-06-19 15:24:42 +02:00
Bernd StorathandGitHub e7ea65a898 various improvements (#2671)
* improve oauth config logic

* expire pending login

* move sort to backend
2026-06-18 11:14:20 +02:00
032347648d improve security against injection (#2669)
* improve security against injection

* fix shell injection through ipv4, ipv6 address

Co-authored-by: 7megaumka7 <116706490+7megaumka7@users.noreply.github.com>

---------

Co-authored-by: 7megaumka7 <116706490+7megaumka7@users.noreply.github.com>
2026-06-16 16:00:25 +02:00
Bernd StorathandGitHub 5f54fa3e58 improve totp security (#2668)
improve security
2026-06-16 14:12:00 +02:00
Bernd Storath 30498520d2 retry docker push step 2026-06-16 13:12:40 +02:00
Bernd StorathandGitHub 47c6f476ac refactor: replace mkdocs with zensical (#2667)
* move docs to zensical

* fix requirements

* fixes

* remove manual font loading

does not work properly with mike

* switch back to pymdownx.blocks.admonition

* revert back

* remove css

* add note about privacy plugin

* pr review findings
2026-06-16 12:56:54 +02:00
09cf208933 fix: improve buttons (#2666)
improve buttons

Co-authored-by: anhhna <anhhna@gmail.com>
2026-06-15 09:11:10 +02:00
Daeho RoandGitHub e9252be45c i18n: add missing Korean (#2665) 2026-06-15 08:34:09 +02:00
Bernd StorathandGitHub 66b292b11b improve security (#2661)
* disable basic auth when password auth disabled

* clarify otl security
2026-06-12 15:55:22 +02:00
Bernd Storath bb5aff1919 fix lint 2026-06-12 15:27:38 +02:00
Bernd Storath 8add67a4cc update packages 2026-06-12 15:24:52 +02:00
ac547bbf7c feat: oauth integration (#2659)
* feat: add Google OAuth login support (#2625)

* 🔧 Add login via google

* 🔧 Update code style and docs

* Add fix for db migrate

* 🔧 Update docker-compose

* 🔧 Fix sqlite"

* 🔧 Update docker-compose

* ⚰️ Remove environments

* 🔧 Fix: remove ensureGoogleIdColumn workaround from sqlite.ts

* 🔧 Remove space

* move oauth section

* add openid client

* wip make oauth more generic

* properly allow multiple providers

* fix type import

* github login flow

* adjust github logo with theme

* move docs to own page

* nullable password, prevent timing attack

this prevents timing attacks by always checking hash even if there is none
prevents using basic auth if 2fa is enabled

* support generic oidc

* add ability to set password for oidc users

this allows oidc users to add password login
cant be removed after

* move password login route

move password login route from /api/session to /api/auth/password
align with oauth

* unique index on oauth

* link/unlink logic

* improve docs

* support allowed domains

* support auto register

* refactoring

* disable pw auth

* move 2fa to its own page

* 2fa for oauth, rework 2fa system

* fix design, fix link

Closes #2650

* add auto launch

* improve docs

* improvements

---------

Co-authored-by: Daniel Molenda <dm@fotc.com>
2026-06-12 13:38:32 +02:00
theis.gaedigk 8c70c24205 Merge remote-tracking branch 'upstream/master'
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Edge / Build Docker (map[os:ubuntu-24.04-arm platform:linux/arm64]) (push) Has been cancelled
Edge / Build Docker (map[os:ubuntu-latest platform:linux/amd64]) (push) Has been cancelled
Lint / Check Docs (push) Has been cancelled
Edge / Merge & Deploy Docker (push) Has been cancelled
Edge / Build & Deploy Docs (push) Has been cancelled
Lint / Lint (format:check) (push) Has been cancelled
Lint / Lint (lint) (push) Has been cancelled
Lint / Lint (typecheck) (push) Has been cancelled
2026-06-03 16:30:28 +02:00
c70ad1d08b Small code quality improvements (#2553)
* Small code quality improvements

- Fix misleading JSDoc comment in cache.ts
- Mitigate timing-based username enumeration in Basic auth
- Extract duplicated TOTP configuration into private method
- Replace manual peer counter with clients.length in Prometheus metrics
- Simplify isValidPasswordHash return expression

* reset session.ts

this is currently worked on in the dev-oauth branch

* reset password.ts

no need to change

* specify unit for cache function

* remove type assertion

---------

Co-authored-by: Anghios <Anghios@users.noreply.github.com>
Co-authored-by: Bernd Storath <bernd@berndstorath.de>
2026-06-03 14:43:32 +02:00
minhducsun2002andGitHub d0566a1df9 Support disabling version check (#2648)
* Support disabling version check

* Update docs

* Move the bypass logic back to update checking function

* fix linting

* fix linting (again)
2026-06-03 14:42:15 +02:00
Bernd StorathandGitHub bc95a2851f chore: rework useSubmit (#2649)
rework useSubmit
2026-06-03 10:08:07 +02:00
Bernd Storath e03d743307 update packages 2026-06-01 08:54:27 +02:00
Bernd Storath 99357848e5 remove dependabot for npm
npm updates are handled manually to correctly dedupe
2026-05-26 08:31:07 +02:00
Ankit AgarwalandGitHub c41ae0d4c5 Add Hindi translation (#2632) 2026-05-26 08:29:18 +02:00
ふぁandGitHub 66f8bde206 Add Japanese translation (#2642) 2026-05-26 08:28:48 +02:00
Bernd Storath b3afb9ac1b update packages 2026-05-26 08:21:10 +02:00
theis.gaedigk 9581e6eacb Merge remote-tracking branch 'upstream/master'
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Edge / Build Docker (map[os:ubuntu-24.04-arm platform:linux/arm64]) (push) Has been cancelled
Edge / Build Docker (map[os:ubuntu-latest platform:linux/amd64]) (push) Has been cancelled
Lint / Check Docs (push) Has been cancelled
Edge / Merge & Deploy Docker (push) Has been cancelled
Edge / Build & Deploy Docs (push) Has been cancelled
Lint / Lint (format:check) (push) Has been cancelled
Lint / Lint (lint) (push) Has been cancelled
Lint / Lint (typecheck) (push) Has been cancelled
2026-05-22 23:32:00 +02:00
Bernd StorathandGitHub 90e2bbe0a6 patch wg-quick. fix sysctl (#2630)
no unneeded sysctl
2026-05-21 11:59:46 +02:00
theis.gaedigk 7b5ba95938 edited pngs
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Edge / Build Docker (map[os:ubuntu-24.04-arm platform:linux/arm64]) (push) Has been cancelled
Edge / Build Docker (map[os:ubuntu-latest platform:linux/amd64]) (push) Has been cancelled
Lint / Check Docs (push) Has been cancelled
Edge / Merge & Deploy Docker (push) Has been cancelled
Edge / Build & Deploy Docs (push) Has been cancelled
Lint / Lint (format:check) (push) Has been cancelled
Lint / Lint (lint) (push) Has been cancelled
Lint / Lint (typecheck) (push) Has been cancelled
2026-05-20 13:14:10 +02:00
theis.gaedigk da90d67cc0 changed icons
Edge / Build Docker (map[os:ubuntu-24.04-arm platform:linux/arm64]) (push) Has been cancelled
Edge / Build Docker (map[os:ubuntu-latest platform:linux/amd64]) (push) Has been cancelled
Edge / Merge & Deploy Docker (push) Has been cancelled
Edge / Build & Deploy Docs (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Lint / Check Docs (push) Has been cancelled
Lint / Lint (format:check) (push) Has been cancelled
Lint / Lint (lint) (push) Has been cancelled
Lint / Lint (typecheck) (push) Has been cancelled
2026-05-20 10:37:40 +02:00
HackingAllandGitHub a52da67b38 Adding new translated lines (#2624) 2026-05-19 14:24:37 +02:00
Bernd Storath e513090074 update badges 2026-05-18 12:36:21 +02:00
Bernd Storath 2dc8ba7792 Bump version to 15.3.0 2026-05-18 12:19:51 +02:00
Bernd StorathandGitHub 4e8cccb4c7 replace debug with obug (#2619)
* patch unenv

* replaces debug with obug

reverts unenv patch
2026-05-18 09:40:36 +02:00
Bernd Storath e57b0977d3 update packages 2026-05-18 08:14:09 +02:00
Bernd Storath b8be53c3f7 fix build 2026-05-13 09:16:24 +02:00
Bernd Storath 0794413191 update packages 2026-05-13 09:02:56 +02:00
Bernd Storath 261b0d6b8f Bump version to 15.3.0-beta.3 2026-05-07 13:59:19 +02:00
Ming MakandGitHub f656d57d20 Translation: update Traditional Chinese (zh-HK) localization (#2603)
* Update Chinese (HK) translations for clarity and completion

* Fix spacing

* Update zh-HK.json

Taking reference from zh_TW, making awg clearer

* Remove duplicate 'search' entry in zh-HK.json

* Fix translation typos in zh-HK locale

Replaced "户" with "戶".
2026-05-07 13:51:37 +02:00
Bernd Storath 46074fea1c update packages 2026-05-05 10:57:30 +02:00
05c655ede9 fix(ui):Error pops up when enabling disabled client (#2594)
* fix(ui):Error pops up when enabling disabled client

Action is prevented and a clear message is displayed in Web UI

* fix formatting

* fix type issue

* fix formatting

---------

Co-authored-by: Bernd Storath <999999bst@gmail.com>
2026-05-04 09:21:18 +02:00
Bernd Storath ebcc42cc49 fix build 2026-04-27 09:07:21 +02:00
EvgeniyandGitHub be8d24e492 Fix: Add trailing newline to Prometheus metrics output (#2573)
Fix prometheus metrics
2026-04-27 08:30:03 +02:00
Bernd Storath 9682dedea7 update packages 2026-04-27 08:28:17 +02:00
Bernd Storath 5eb80fe3c1 update packages 2026-04-13 14:16:05 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dd9da2a067 build(deps): bump pnpm/action-setup from 5 to 6 (#2574)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 5 to 6.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v5...v6)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:35:40 +02:00
Bernd Storath 15111ecd62 update packages 2026-04-13 13:02:53 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e9f4b4650b build(deps): bump pnpm/action-setup from 4 to 5 (#2570)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 4 to 5.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v4...v5)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 16:06:15 +02:00
Bernd Storath e3e4049f8e update packages 2026-04-08 15:51:37 +02:00
Bernd Storath 3fb9adbf6f Bump version to 15.3.0-beta.2 2026-04-07 11:46:03 +02:00
Bernd StorathandGitHub cd9db1563d fix: mobile UI (#2569)
* improve mobile ui

* general cleanup

* cleanup, improvements

* fix hydration mismatch
2026-04-07 11:34:49 +02:00
b5c30f5dbe i18n: add Vietnamese language support (#2568)
* i18n: add Vietnamese translation

* i18n: fix lint

* i18n: import fix

---------

Co-authored-by: meow <meow@air.local>
2026-04-07 11:22:13 +02:00
adi82bdgandGitHub 1eb9527175 Update Polish translations for various terms (#2566) 2026-04-02 13:25:55 +02:00
NikolasandGitHub cd890c1f0f Update uk.json (#2559) 2026-03-25 08:54:53 +01:00
2a78b30aeb Config fix for #2208 : Document network interface fix for multi-network configuations (#2555)
* document fix for #2208

* typo fix

* prettier

* fix code block format

* fix indentation

---------

Co-authored-by: Timothy Pillow <timothy.pillow@swisscom.com>
Co-authored-by: Bernd Storath <999999bst@gmail.com>
Co-authored-by: Bernd Storath <32197462+kaaax0815@users.noreply.github.com>
2026-03-23 09:59:17 +01:00
ChiahongandGitHub 9a843087c3 i18n(zh-tw): Update Traditional Chinese translation (#2558) 2026-03-23 07:49:12 +01:00
Bernd Storath 483b63bba6 update packages 2026-03-16 08:28:58 +01:00
Alexis-LoskoutoffandGitHub 13942c97b2 i18n(fr): Update French translation (#2544) 2026-03-16 08:04:34 +01:00
MeCiasandGitHub 82c64e506e Lang(de): Added missing translations (#2543)
* Update de.json

Lang(de): Added missing translations

* Lang(de): Added missing translations and Comma

* Lang(de): Added missing translations and Comma and identations
2026-03-14 19:49:28 +01:00
Aarón Rosa DíazandGitHub 9b3d919168 i18n(es): Updated Spanish translation (#2540)
Updated Spanish translations for various UI elements

Updated and completed all remaining Spanish translations, including support for AmneziaWG 2.0. Following: https://wg-easy.github.io/wg-easy/v15.2/contributing/translation/
2026-03-13 07:47:09 +01:00
3eaf0d01dc i18n: improve Simplified Chinese translation (#2541)
* Fix zh-CN translate

* i18n: improve Simplified Chinese translation

---------

Co-authored-by: yanghuanglin <yanghuanglin@qq.com>
2026-03-13 07:46:25 +01:00
Bernd Storath 414e9a114b Bump version to 15.3.0-beta.1 2026-03-11 09:06:54 +01:00
Maksim M.andGitHub 2d28d87c5c i18n(ru): Update Russian Lang (#2538)
* i18n(ru): Add firewall and download options

- Updated translation, added and translated all missing values from en.json

* i18n(ru): Minor changes

- Minor edits that don't change the translation

* i18n(ru): Terminology changes

- "Интерфейс" (Interface) has been renamed to "Сетевой интерфейс" (Network Interface) to avoid confusion with the Web UI, which is often referred to simply as "interface" in Russian.
2026-03-11 08:51:48 +01:00
Bernd StorathandGitHub 76c2233e46 chore: update node to krypton (24) (#2536)
update to node krypton
2026-03-10 10:45:10 +01:00
Stephan PaternotteandGitHub abedf9f38e Dutch translation updated (#2535)
* Dutch translation updated

Update nl.json updated re. "copy & download qr code as png"

* Update nl.json

Couple of trailing comma's added. Valid json now.
2026-03-10 10:42:33 +01:00
25f3fa3c0f Update Turkish localization strings (#2523)
* Update Turkish localization strings

* Update src/i18n/locales/tr.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/i18n/locales/tr.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/i18n/locales/tr.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/i18n/locales/tr.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/i18n/locales/tr.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/i18n/locales/tr.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-10 09:36:46 +01:00
Bernd Storath c3c51f8088 update packages 2026-03-09 08:01:17 +01:00
Bernd StorathandGitHub 8ea2b635c1 feat: change hooks to textareas (#2522)
* hooks are now textareas

* remove newlines in client config
2026-03-05 14:52:55 +01:00
Bernd StorathandGitHub bc4dfd03df feat: copy & download qr code as png (#2521)
* copy & download qr code as png

* i18n, accessibility

* improve error handling
2026-03-05 13:39:31 +01:00
Bernd Storath 7cde04de81 docs: fix faq urls
introduced in #2418
2026-03-05 13:38:12 +01:00
Bernd StorathandGitHub 5228734c98 feat(cli): add command to show qr code (#2518)
* refactor cli, add commands

* add docs

* improve

* fix ec mode order
2026-03-05 11:53:27 +01:00
47f81dd66a Feature/client firewall filtering (#2418)
* Add per-client firewall filtering

Implement server-side firewall rules to restrict client network access,
allowing administrators to enforce security policies that cannot be
bypassed by clients modifying their local configuration.

This feature addresses the limitation where "Allowed IPs" only controls
client-side routing but doesn't prevent clients from accessing networks
they shouldn't reach. The firewall rules are enforced on the server
using iptables/ip6tables and provide true access control.

Features:
- Opt-in via "Enable Per-Client Firewall" toggle in admin interface
- Per-client "Firewall Allowed IPs" field for granular control
- Support for IPs, CIDRs, and port-based filtering
- Protocol specification: TCP, UDP, or both (default)
- IPv4 and IPv6 dual-stack support
- Falls back to client's allowedIps when firewallIps is empty
- Clean separation of routing (allowedIps) from security (firewallIps)

Supported formats:
- 10.10.0.3 (single IP)
- 10.10.0.0/24 (CIDR range)
- 192.168.1.5:443 (IP with port, both TCP+UDP)
- 192.168.1.5:443/tcp (IP with specific protocol)
- [2001:db8::1]:443 (IPv6 with port)

Implementation:
- New database columns: firewall_enabled (interfaces), firewall_ips (clients)
- Migration 0003_add_firewall_filtering for schema updates
- firewall.ts utility for iptables chain management (WG_CLIENTS chain)
- Integration into WireGuard.ts for automatic rule application
- UI components with conditional rendering based on firewall toggle

Technical details:
- Uses custom WG_CLIENTS iptables chain for isolation
- Rebuild strategy: flush and recreate all rules on config save
- Mutex protection via rebuildInProgress/rebuildQueued flags
- Graceful cleanup when firewall is disabled
- No new dependencies (uses existing is-ip, is-cidr packages)

* added Comprehensive documentation in README and docs/ for firewall
filtering

* validate firewall IPs

* check for iptables before enabling the firewall and inform the user if
it is missing

* updated firewall docs

* fix imports

* remove extra import

* Document all allowed IP/cidr/port/proto combinations that are allowed
and check on save

* add note on firewall being experimental and how to opt a single client
out of the firewall.

* cleanup more imports

* add tests

* Fix firewall IPv6 validation and test expectations

Updated validation to correctly handle plain and bracketed IPv6 addresses, and fixed test to expect string from schema instead of object.

* added comments to firewall rules and updated tests

* fix auto-import

* fix typescript errors

* recreate sql migrations and rebase

* improve tests, typechecking, documentation

* fix formatting, fix types

* improve type

* added note for including host's IP in client firewall

* updated language to include cidr and protocol options

* another language update

* refer to docs for firewall allowed IPs

---------

Co-authored-by: Bernd Storath <999999bst@gmail.com>
2026-03-05 08:47:46 +01:00
Filip RichterandGitHub e5b2c3d10b Add Czech language support (#2487)
* Add Czech localization file for i18n

* Add Czech locale support to i18n configuration

* Add Czech language support to nuxt.config.ts

* Update Czech translation for 'hooks' key
2026-03-03 11:30:06 +01:00
Bernd Storath 059a0ccffc update packages 2026-03-02 08:18:59 +01:00
Maksim M.andGitHub 8c9c54c8b2 i18n(ru): Add missing validNumberRange and update H1-H4 descriptions (#2501)
Update Russian translations - H Descriptions
2026-03-02 08:09:12 +01:00
Stephan PaternotteandGitHub 02ce6f0a65 Update nl.json - hxDescriptions updated (#2498)
AmneziaWG 2.0: support for H1-H4 ranges (#2480)
2026-02-27 07:47:37 +01:00
Bernd Storath 48682e1abd update packages 2026-02-23 08:12:21 +01:00
Stephan PaternotteandGitHub 044dd34dcc Update nl.json (#2490)
AmneziaWG 2.0: support for H1-H4 ranges (#2480) TRANSLATED
2026-02-21 20:34:09 +01:00
a469ac6897 AmneziaWG 2.0: support for H1-H4 ranges (#2480)
* AmneziaWG 2.0: support for H1-H4 ranges

## Changes:
```
- [+] Added support for H1-H4 ranges
- [!] Fixed interface fields order (H1-H4 goes before I1-I5)
```

## Known issues:
```
- [!] no check for unique/overlap of H1-H4 values on settings apply:
  settings will be applied but wg interface will crash with "Invalid argument" error
```

* AmneziaWG 2.0: support for H1-H4 ranges

## Changes:
```
- [+] Added support for H1-H4 ranges
- [!] Fixed interface fields order (H1-H4 goes before I1-I5)
```

## Known issues:
```
- [!] no check for unique/overlap of H1-H4 values on settings apply:
  settings will be applied but wg interface will crash with "Invalid argument" error
```

* AmneziaWG 2.0: support for H1-H4 ranges

## Changes:
```
- [+] Added support for H1-H4 ranges
- [!] Fixed interface fields order (H1-H4 goes before I1-I5)
```

## Known issues:
```
- [!] no check for unique/overlap of H1-H4 values on settings apply:
  settings will be applied but wg interface will crash with "Invalid argument" error
```

* Update types.ts

Lint fixes

---------

Co-authored-by: CthulhuVRN <alexander@ptitsyn.info>
2026-02-17 13:27:22 +01:00
Bernd Storath 1178d23659 update packages 2026-02-16 09:11:19 +01:00
Bernd Storath b3cc1ce839 reduce container size
from around 700mb to 400mb
2026-02-11 15:36:29 +01:00
Bernd Storath 71aaec93ef add unit tests
this adds the groundwork to start unit testing some modules
2026-02-11 15:23:04 +01:00
HackingAllandGitHub 7a219b73d4 Adding galician (#2473)
* Adding galego -> gl.json

* Update i18n.config.ts

* Update nuxt.config.ts
2026-02-10 11:18:02 +01:00
Bare7aandGitHub c456c5e7dd Add Bulgarian translation (#2466) 2026-02-09 08:00:38 +01:00
Bernd Storath a5880cc0b8 update packages 2026-02-09 08:00:07 +01:00
Bernd Storath 5fca628ebd change date in copyright footer 2026-02-06 15:32:39 +01:00
Bernd Storath 7ab297c366 Bump version to 15.2.2 2026-02-06 14:55:20 +01:00
Bernd StorathandGitHub c5de8f0f44 cli: reset 2fa on admin reset (#2461)
reset 2fa on cli reset
2026-02-06 12:28:13 +01:00
Bernd StorathandGitHub c0641889cf replace watchtower (#2456)
update to maintained fork
2026-02-04 08:24:48 +01:00
Bernd Storath 9141562f91 update tagging convention for latest tag 2026-02-04 08:17:58 +01:00
thebigpotatoeandGitHub d21af70df1 fix: correct help text for prometheus metrics (#2453) 2026-02-03 11:27:03 +01:00
Bernd Storath 56ee86cc1c update packages 2026-02-02 08:27:05 +01:00
Runar IngebrigtsenandGitHub f017b4968c Add Norwegian bokmål translation (#2447)
* add Norwegian bokmål translation

Signed-off-by: Runar Ingebrigtsen <runar@rin.no>

* add nb config, available in UI

Signed-off-by: Runar Ingebrigtsen <runar@rin.no>

---------

Signed-off-by: Runar Ingebrigtsen <runar@rin.no>
2026-02-02 08:18:31 +01:00
Alexander ChepurnoyandGitHub 6004457666 feat: update client and server interface parameters (#2440) 2026-01-28 09:34:11 +01:00
Bernd StorathandGitHub 1a5a0180ea Fix LangSelector and ClientCard z-index (#2434)
fix client card
2026-01-27 08:24:53 +01:00
Ayush ChotheandGitHub c732f149e6 feat: Add wireguard-go package in Dockerfile (#2419)
* feat: Add `wireguard-go` package in `Dockerfile`

* feat: add amneziawg-go
Co-authored-by: cany748 <cany748@gmail.com>
2026-01-27 08:08:24 +01:00
Bernd Storath 4819480eb0 update packages 2026-01-26 09:02:25 +01:00
fc7ab0dc21 feat(docs): edit amnezia.md (#2430)
* feat(docs): edit amnezia.md

* Update docs/content/advanced/config/amnezia.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update docs/content/advanced/config/amnezia.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-26 08:43:09 +01:00
eb6b96c0f1 lang: Add dutch language (#2413)
add dutch language

Co-authored-by: Stephan Paternotte <Stephan-P@users.noreply.github.com>
2026-01-21 09:39:18 +01:00
Bernd Storath f62fad9c40 update packages 2026-01-20 08:51:19 +01:00
Maksim M.andGitHub e9a472c8f7 fix(i18n/ru): use "мусор" for AmneziaWG junk packets (#2402)
The RU translation changed "junk" from "мусорный" to "шумовой".
Amnezia authors consistently describe these packets/bytes as "мусор" in their docs and Habr posts. Align wording with upstream terminology to avoid confusion for RU users.
2026-01-15 08:14:38 +01:00
Bernd Storath 552e2b8cbf update screenshot 2026-01-14 09:53:04 +01:00
Bernd Storath a0b4192cbd Bump version to 15.2.1 2026-01-14 08:51:01 +01:00
Bernd Storath 32a055093a exclude i18n from setup middleware 2026-01-13 13:15:46 +01:00
Bernd StorathandGitHub 51558c7027 refactor: session handling (#2398)
* refactor session handling

* simplify
2026-01-13 10:11:13 +01:00
binnichtaktivandGitHub b85286f0ab Lang(de): Improve and add missing translations (#2393)
* add missing german translations

* fix typos and improve existing translations

* fix punctuation

* fix last overlooked typos
2026-01-13 08:21:08 +01:00
Bernd Storath 48f3fbd715 always generate awg h1-h5 2026-01-13 08:07:13 +01:00
Bernd Storath 458f66818a fix search magnifying icon 2026-01-12 12:04:50 +01:00
Bernd Storath 7964dc7993 Bump version to 15.2.0 2026-01-12 08:28:23 +01:00
RaffaelHoldandGitHub 0ac5d7d461 feat(docs): Extend docs for routed setup with nftables (#2380)
* Extend docs for routed setup with nftables

When using nftables in a routed setup different up and down hooks need to be used. 
To limit interaction with docker managed chains a custom WG_EASY chain is added as a jump target.
Since nft only supports deletion via handles awk is needed to get the handle of the jump rule for deletion

* Remove link to podman-nft

* Fix formatting according to prettier rules

* Add additional whitespace
2026-01-12 08:21:18 +01:00
Bernd Storath 826914a4f3 update packages 2026-01-12 08:19:01 +01:00
Bernd StorathandGitHub 261da431e7 Fix: reset onetimelink expiration instead of failing (#2370)
* update expiresAt instead of failing

* add changelog
2025-12-29 19:12:53 +01:00
Bernd StorathandGitHub 94b33abf5e Remove armv7 support (#2369)
remove armv7 support
2025-12-29 18:54:47 +01:00
Bernd Storath 8325056ccc update lockfile 2025-12-29 18:47:48 +01:00
Bernd Storath 81a1b2c907 update packages 2025-12-29 18:36:51 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fc8f89fb83 build(deps): bump actions/download-artifact from 6 to 7 (#2343)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 07:58:23 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d846c7745f build(deps): bump actions/upload-artifact from 5 to 6 (#2344)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 07:58:12 +01:00
Bernd Storath 61c6fd6c02 update packages 2025-12-15 08:03:25 +01:00
David DIVERRESandGitHub abe5708058 Update fr.json (#2326)
Update fr.json - Add missing translations and improve existing ones

- Add missing keys: client.delete, client.search, client.config, client.viewConfig
- Add complete copy section (4 keys)
- Add complete awg section (27 keys for AmneziaWG parameters)
- Fix terminology: "double facteur" → "deux facteurs" (standard French)
- Fix zod.generic.validBoolean translation
- Fix zod.generic.stringMin capitalization and wording
- Translate zod.client.id to French
- Improve admin.general route descriptions
2025-12-08 08:51:38 +01:00
Bernd Storath 626339bddb update packages 2025-12-08 08:30:53 +01:00
381ae23c07 Update ru (#2324)
* Update ru.json

Full revision of the Russian localization for the AWG configuration block, including terminology corrections, improved consistency, and clarified parameter descriptions.

* Update ru.json

* fix formatting

---------

Co-authored-by: Bernd Storath <999999bst@gmail.com>
2025-12-05 15:59:12 +01:00
NikolasandGitHub 52382d1d7a Update uk.json (#2323) 2025-12-05 07:35:15 +01:00
68e5216d4b Update ru.json (#2319)
* Update ru.json

* format

---------

Co-authored-by: Bernd Storath <999999bst@gmail.com>
2025-12-02 12:26:04 +01:00
Bernd Storath ceff95b336 Bump version to 15.2.0-beta.3 2025-12-01 10:12:01 +01:00
Alexander ChepurnoyandGitHub 782d1c215f feat(docs): edit amnezia page (#2292)
* feat(docs): edit amnezia page

* Fix AmneziaWG documentation link

* Update AmneziaWG client compatibility information
2025-12-01 08:23:01 +01:00
Bernd Storath e8e26cfe10 update packages 2025-12-01 07:54:59 +01:00
Bernd StorathandGitHub 400d4d992e Fix light mode admin menu active text color (#2307)
* fix color

* remove duplicates
2025-11-25 08:26:18 +01:00
Bernd Storath b08df55321 fix build 2025-11-24 08:03:37 +01:00
Bernd Storath b26a8110e0 update packages 2025-11-24 08:01:11 +01:00
692f550596 Improve zh-CN translate (#2298)
Fix zh-CN translate

Co-authored-by: yanghuanglin <yanghuanglin@qq.com>
2025-11-24 07:57:17 +01:00
ChiahongandGitHub badae8b8e4 fix(i18n): Add missing translation for delete action (#2295) 2025-11-21 17:55:11 +01:00
NikolasandGitHub 7f89bde99e Update uk.json (#2293) 2025-11-21 08:02:47 +01:00
Bernd Storath 326717444b Bump version to 15.2.0-beta.2 2025-11-18 15:05:42 +01:00
Bernd StorathandGitHub 4e4bfc75e3 feat: add config btn and modal to view and copy config (#2289)
* add view config btn and modal

* show loading state

* add note about keyboard
2025-11-18 11:36:46 +01:00
Bernd StorathandGitHub 5c97a8ba73 try all qr ecc levels (#2288)
try ecc levels
2025-11-18 09:25:57 +01:00
Bernd Storath cba7a160ea intellicode deprecated 2025-11-17 08:04:10 +01:00
4a75e1379d Update uk.json (#2286)
* Update uk.json

* fix formatting

---------

Co-authored-by: Bernd Storath <999999bst@gmail.com>
2025-11-17 07:54:50 +01:00
ChiahongandGitHub 10a140d188 feat(i18n): Add Traditional Chinese (Taiwan, zh-TW) support (#2285) 2025-11-17 07:53:03 +01:00
256 changed files with 19046 additions and 6225 deletions
+34 -11
View File
@@ -1,28 +1,51 @@
<!--- Provide a general summary of your changes in the Title above --> <!-- Provide a general summary of your changes in the Title above -->
## Description ## Description
<!--- Describe your changes in detail -->
<!-- Describe your changes in detail -->
<!-- Focus on the implementation and any important technical decisions. -->
## Motivation and Context ## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. --> <!-- Why is this change needed? What problem does it solve? -->
<!-- Use "Fixes #123" or "Closes #123" when this pull request resolves an issue. -->
## How has this been tested? ## How has this been tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->
## Screenshots (if appropriate): <!-- Please describe in detail how you tested your changes. -->
<!-- Include details of your testing environment, tests ran to see how -->
<!-- your change affects other areas of the code, etc. -->
## Screenshots:
<!-- Add screenshots or recordings for visual changes. -->
<!-- Remove this section if it is not applicable. -->
## Types of changes ## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality) - [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Refactoring (change which does not alter existing functionality)
- [ ] Documentation (changes to documentation only)
## Checklist: ## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> <!-- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] I have reviewed my own changes.
- [ ] My code follows the code style of this project. - [ ] My code follows the code style of this project.
- [ ] I have added or updated tests where appropriate.
- [ ] My changes do not include unrelated modifications.
- [ ] My change requires a change to the documentation. - [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly. - [ ] I have updated the documentation accordingly.
## AI Disclosure:
<!-- See the AI contribution policy: https://github.com/wg-easy/wg-easy/blob/master/contributing.md#ai-contribution-policy -->
<!-- Disclose whether AI tools were used -->
<!-- Which tools were used -->
<!-- The extent to which they were used -->
-10
View File
@@ -5,13 +5,3 @@ 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"
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v7
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v4 uses: github/codeql-action/init@v4
+22 -16
View File
@@ -18,10 +18,10 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- platform: linux/arm64 - platform: linux/arm64
os: ubuntu-24.04-arm os: ubuntu-24.04-arm
- platform: linux/arm/v7 # - platform: linux/arm/v7
os: ubuntu-24.04-arm # os: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- name: Prepare - name: Prepare
run: | run: |
@@ -30,7 +30,7 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: | images: |
ghcr.io/wg-easy/wg-easy ghcr.io/wg-easy/wg-easy
@@ -38,21 +38,21 @@ jobs:
latest=false latest=false
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push by digest - name: Build and push by digest
id: build id: build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
platforms: ${{ matrix.arch.platform }} platforms: ${{ matrix.arch.platform }}
@@ -69,7 +69,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v7
with: with:
name: digests-${{ env.PLATFORM_PAIR }} name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -85,32 +85,32 @@ jobs:
needs: docker-build needs: docker-build
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v6 uses: actions/download-artifact@v8
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: digests-* pattern: digests-*
merge-multiple: true merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Codeberg - name: Login to Codeberg
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: codeberg.org registry: codeberg.org
username: ${{ secrets.CODEBERG_USER }} username: ${{ secrets.CODEBERG_USER }}
password: ${{ secrets.CODEBERG_PASS }} password: ${{ secrets.CODEBERG_PASS }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: | images: |
ghcr.io/wg-easy/wg-easy ghcr.io/wg-easy/wg-easy
@@ -123,8 +123,14 @@ jobs:
- name: Create manifest list and push - name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests working-directory: ${{ runner.temp }}/digests
run: | run: |
n=3
for i in $(seq 1 $n); do
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *) $(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *) && break
[ "$i" = "$n" ] && exit 1
sleep 10
done
- name: Inspect image - name: Inspect image
run: | run: |
@@ -138,7 +144,7 @@ jobs:
contents: write contents: write
needs: docker-merge needs: docker-merge
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
+22 -16
View File
@@ -25,10 +25,10 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- platform: linux/arm64 - platform: linux/arm64
os: ubuntu-24.04-arm os: ubuntu-24.04-arm
- platform: linux/arm/v7 # - platform: linux/arm/v7
os: ubuntu-24.04-arm # os: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
with: with:
ref: master ref: master
@@ -39,7 +39,7 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: | images: |
ghcr.io/wg-easy/wg-easy ghcr.io/wg-easy/wg-easy
@@ -47,21 +47,21 @@ jobs:
latest=false latest=false
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push by digest - name: Build and push by digest
id: build id: build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
platforms: ${{ matrix.arch.platform }} platforms: ${{ matrix.arch.platform }}
@@ -78,7 +78,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v7
with: with:
name: digests-${{ env.PLATFORM_PAIR }} name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -94,32 +94,32 @@ jobs:
needs: docker-build needs: docker-build
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v6 uses: actions/download-artifact@v8
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: digests-* pattern: digests-*
merge-multiple: true merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Codeberg - name: Login to Codeberg
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: codeberg.org registry: codeberg.org
username: ${{ secrets.CODEBERG_USER }} username: ${{ secrets.CODEBERG_USER }}
password: ${{ secrets.CODEBERG_PASS }} password: ${{ secrets.CODEBERG_PASS }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: | images: |
ghcr.io/wg-easy/wg-easy ghcr.io/wg-easy/wg-easy
@@ -132,8 +132,14 @@ jobs:
- name: Create manifest list and push - name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests working-directory: ${{ runner.temp }}/digests
run: | run: |
n=3
for i in $(seq 1 $n); do
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *) $(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *) && break
[ "$i" = "$n" ] && exit 1
sleep 10
done
- name: Inspect image - name: Inspect image
run: | run: |
@@ -147,7 +153,7 @@ jobs:
contents: write contents: write
needs: docker-merge needs: docker-merge
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
with: with:
ref: master ref: master
+7 -7
View File
@@ -21,10 +21,10 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- platform: linux/arm64 - platform: linux/arm64
os: ubuntu-24.04-arm os: ubuntu-24.04-arm
- platform: linux/arm/v7 # - platform: linux/arm/v7
os: ubuntu-24.04-arm # os: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- name: Prepare - name: Prepare
run: | run: |
@@ -32,20 +32,20 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image - name: Build Docker Image
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
push: false push: false
+22 -16
View File
@@ -26,10 +26,10 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- platform: linux/arm64 - platform: linux/arm64
os: ubuntu-24.04-arm os: ubuntu-24.04-arm
- platform: linux/arm/v7 # - platform: linux/arm/v7
os: ubuntu-24.04-arm # os: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- name: Prepare - name: Prepare
run: | run: |
@@ -38,7 +38,7 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: | images: |
ghcr.io/wg-easy/wg-easy ghcr.io/wg-easy/wg-easy
@@ -46,21 +46,21 @@ jobs:
latest=false latest=false
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push by digest - name: Build and push by digest
id: build id: build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
platforms: ${{ matrix.arch.platform }} platforms: ${{ matrix.arch.platform }}
@@ -77,7 +77,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v7
with: with:
name: digests-${{ env.PLATFORM_PAIR }} name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -95,32 +95,32 @@ jobs:
needs: docker-build needs: docker-build
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v6 uses: actions/download-artifact@v8
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: digests-* pattern: digests-*
merge-multiple: true merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Codeberg - name: Login to Codeberg
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: codeberg.org registry: codeberg.org
username: ${{ secrets.CODEBERG_USER }} username: ${{ secrets.CODEBERG_USER }}
password: ${{ secrets.CODEBERG_PASS }} password: ${{ secrets.CODEBERG_PASS }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: | images: |
ghcr.io/wg-easy/wg-easy ghcr.io/wg-easy/wg-easy
@@ -135,8 +135,14 @@ jobs:
- name: Create manifest list and push - name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests working-directory: ${{ runner.temp }}/digests
run: | run: |
n=3
for i in $(seq 1 $n); do
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *) $(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *) && break
[ "$i" = "$n" ] && exit 1
sleep 10
done
- name: Inspect image - name: Inspect image
run: | run: |
@@ -152,7 +158,7 @@ jobs:
contents: write contents: write
needs: docker-merge needs: docker-merge
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
+8 -8
View File
@@ -14,17 +14,17 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v7
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v6
name: Install pnpm name: Install pnpm
with: with:
run_install: false run_install: false
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v6 uses: actions/setup-node@v7
with: with:
node-version: "lts/jod" node-version: "lts/krypton"
check-latest: true check-latest: true
cache: "pnpm" cache: "pnpm"
@@ -47,17 +47,17 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v7
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v6
name: Install pnpm name: Install pnpm
with: with:
run_install: false run_install: false
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v6 uses: actions/setup-node@v7
with: with:
node-version: "lts/jod" node-version: "lts/krypton"
check-latest: true check-latest: true
cache: "pnpm" cache: "pnpm"
+3
View File
@@ -1,3 +1,6 @@
.DS_Store .DS_Store
*.swp *.swp
node_modules node_modules
docs/site
docs/.cache
-1
View File
@@ -3,7 +3,6 @@
"aaron-bond.better-comments", "aaron-bond.better-comments",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"antfu.goto-alias", "antfu.goto-alias",
"visualstudioexptteam.vscodeintellicode",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"yoavbls.pretty-ts-errors", "yoavbls.pretty-ts-errors",
"bradlc.vscode-tailwindcss", "bradlc.vscode-tailwindcss",
+4 -1
View File
@@ -26,5 +26,8 @@
"i18n-ally.sortKeys": false, "i18n-ally.sortKeys": false,
"i18n-ally.keepFulfilled": false, "i18n-ally.keepFulfilled": false,
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",
"editor.gotoLocation.multipleDefinitions": "goto" "editor.gotoLocation.multipleDefinitions": "goto",
"[toml]": {
"editor.defaultFormatter": "tamasfe.even-better-toml"
}
} }
+77 -5
View File
@@ -7,19 +7,87 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## Added ### Added
- Support disabling version check (https://github.com/wg-easy/wg-easy/pull/2648)
- OAuth integration (https://github.com/wg-easy/wg-easy/pull/2659)
### Fixed
- Button styling (https://github.com/wg-easy/wg-easy/pull/2666)
- Escape Prometheus label values (https://github.com/wg-easy/wg-easy/pull/2702)
- Skip ip6tables for firewall when IPv6 is disabled (https://github.com/wg-easy/wg-easy/pull/2701)
### Changed
- Only set sysctl if needed (https://github.com/wg-easy/wg-easy/pull/2630)
- Improve security (https://github.com/wg-easy/wg-easy/pull/2661, https://github.com/wg-easy/wg-easy/pull/2668, https://github.com/wg-easy/wg-easy/pull/2669)
- Replace mkdocs with zensical (https://github.com/wg-easy/wg-easy/pull/2667)
- Migrate to Nuxt 4 (https://github.com/wg-easy/wg-easy/pull/2660)
## [15.3.0] - 2026-05-18
### Added
- AWG: support for H1-H4 ranges (https://github.com/wg-easy/wg-easy/pull/2480)
- Client Firewall (https://github.com/wg-easy/wg-easy/pull/2418)
- CLI: Show QR code (https://github.com/wg-easy/wg-easy/pull/2518)
- Copy QR code to clipboard / save as png (https://github.com/wg-easy/wg-easy/pull/2521)
### Fixed
- Add trailing newline to Prometheus metrics output (https://github.com/wg-easy/wg-easy/pull/2573)
- Correctly use DEBUG env var (https://github.com/wg-easy/wg-easy/pull/2619)
### Changed
- Hooks are now Textareas (https://github.com/wg-easy/wg-easy/pull/2522)
- Update to Node Krypton (24) (https://github.com/wg-easy/wg-easy/pull/2536)
- Mobile UI (https://github.com/wg-easy/wg-easy/pull/2569)
- Prevent enabling client when expired (https://github.com/wg-easy/wg-easy/pull/2594)
## [15.2.2] - 2026-02-06
### Added
- Added Userspace WireGuard support (https://github.com/wg-easy/wg-easy/pull/2419)
### Fixed
- LangSelector overlapping with Buttons (https://github.com/wg-easy/wg-easy/pull/2434)
- AmnzeziaWG config parameters (https://github.com/wg-easy/wg-easy/pull/2440)
- OpenMetrics help string format (https://github.com/wg-easy/wg-easy/pull/2453)
- Reset 2fa when resetting admin password (https://github.com/wg-easy/wg-easy/pull/2461)
### Docs
- Replace Watchtower with maintained fork (https://github.com/wg-easy/wg-easy/pull/2456)
## [15.2.1] - 2026-01-14
### Fixed
- Icon in Searchbar (https://github.com/wg-easy/wg-easy/commit/458f66818a400f181e2c6326ede077c8793d71f2)
- Interface save not working (https://github.com/wg-easy/wg-easy/commit/48f3fbd715a889e2425702a8a46332f2752aef91)
- Error Messages in Setup (https://github.com/wg-easy/wg-easy/commit/32a055093a76342c40858d8dcf563b0700a8bd48)
## [15.2.0] - 2026-01-12
### Added
- AmneziaWG integration (https://github.com/wg-easy/wg-easy/pull/2102, https://github.com/wg-easy/wg-easy/pull/2226) - AmneziaWG integration (https://github.com/wg-easy/wg-easy/pull/2102, https://github.com/wg-easy/wg-easy/pull/2226)
- Search / filter box (https://github.com/wg-easy/wg-easy/pull/2170) - Search / filter box (https://github.com/wg-easy/wg-easy/pull/2170)
- `INIT_ALLOWED_IPS` env var (https://github.com/wg-easy/wg-easy/pull/2164) - `INIT_ALLOWED_IPS` env var (https://github.com/wg-easy/wg-easy/pull/2164)
- Show client endpoint (https://github.com/wg-easy/wg-easy/pull/2058) - Show client endpoint (https://github.com/wg-easy/wg-easy/pull/2058)
- Add option to view and copy config (https://github.com/wg-easy/wg-easy/pull/2289)
## Fixed ### Fixed
- Fix download as conf.txt (https://github.com/wg-easy/wg-easy/pull/2269) - Fix download as conf.txt (https://github.com/wg-easy/wg-easy/pull/2269)
- Clean filename for OTL download (https://github.com/wg-easy/wg-easy/pull/2253) - Clean filename for OTL download (https://github.com/wg-easy/wg-easy/pull/2253)
- Text color in admin menu in light mode (https://github.com/wg-easy/wg-easy/pull/2307)
## Changed ### Changed
- Allow lower MTU (https://github.com/wg-easy/wg-easy/pull/2228) - Allow lower MTU (https://github.com/wg-easy/wg-easy/pull/2228)
- Use /32 and /128 for client Cidr (https://github.com/wg-easy/wg-easy/pull/2217) - Use /32 and /128 for client Cidr (https://github.com/wg-easy/wg-easy/pull/2217)
@@ -27,11 +95,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Publish on Codeberg (https://github.com/wg-easy/wg-easy/pull/2160) - Publish on Codeberg (https://github.com/wg-easy/wg-easy/pull/2160)
- Allow empty DNS (https://github.com/wg-easy/wg-easy/pull/2052, https://github.com/wg-easy/wg-easy/pull/2057) - Allow empty DNS (https://github.com/wg-easy/wg-easy/pull/2052, https://github.com/wg-easy/wg-easy/pull/2057)
- Don't include keys in API responses (https://github.com/wg-easy/wg-easy/pull/2015) - Don't include keys in API responses (https://github.com/wg-easy/wg-easy/pull/2015)
- Try all QR ecc levels (https://github.com/wg-easy/wg-easy/pull/2288)
- Update OneTimeLink expiry on reuse (https://github.com/wg-easy/wg-easy/pull/2370)
- Removed ARMv7 support (https://github.com/wg-easy/wg-easy/pull/2369)
## Docs ### Docs
- Add AdGuard Home (https://github.com/wg-easy/wg-easy/pull/2175) - Add AdGuard Home (https://github.com/wg-easy/wg-easy/pull/2175)
- Add Routed (No NAT) docs (https://github.com/wg-easy/wg-easy/pull/2181) - Add Routed (No NAT) docs (https://github.com/wg-easy/wg-easy/pull/2181, https://github.com/wg-easy/wg-easy/pull/2380)
- Add AmneziaWG docs (https://github.com/wg-easy/wg-easy/pull/2108, https://github.com/wg-easy/wg-easy/pull/2292)
## [15.1.0] - 2025-07-01 ## [15.1.0] - 2025-07-01
+23 -11
View File
@@ -1,4 +1,4 @@
FROM docker.io/library/node:jod-alpine AS build FROM docker.io/library/node:krypton-alpine AS build
WORKDIR /app WORKDIR /app
# update corepack # update corepack
@@ -7,7 +7,7 @@ RUN npm install --global corepack@latest
RUN corepack enable pnpm RUN corepack enable pnpm
# Copy Web UI # Copy Web UI
COPY src/package.json src/pnpm-lock.yaml ./ COPY src/package.json src/pnpm-lock.yaml src/pnpm-workspace.yaml ./
RUN pnpm install RUN pnpm install
# Build UI # Build UI
@@ -15,14 +15,22 @@ COPY src ./
RUN pnpm build RUN pnpm build
# Build amneziawg-tools # Build amneziawg-tools
RUN apk add linux-headers build-base git && \ RUN apk add linux-headers build-base go git && \
git clone https://github.com/amnezia-vpn/amneziawg-tools.git && \ git clone https://github.com/amnezia-vpn/amneziawg-tools.git && \
cd amneziawg-tools/src && \ git clone https://github.com/amnezia-vpn/amneziawg-go && \
make cd amneziawg-go && \
make && \
cd ../amneziawg-tools/src && \
make && \
sed -i 's|\[\[ $proto == -4 \]\] && cmd sysctl -q net\.ipv4\.conf\.all\.src_valid_mark=1|[[ $proto == -4 ]] \&\& [[ $(sysctl -n net.ipv4.conf.all.src_valid_mark) != 1 ]] \&\& cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|' ./wg-quick/linux.bash
FROM docker.io/library/node:krypton-alpine AS build-libsql
WORKDIR /app
RUN npm install --no-save --omit=dev libsql
# Copy build result to a new image. # 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:jod-alpine FROM docker.io/library/node:krypton-alpine
WORKDIR /app WORKDIR /app
HEALTHCHECK --interval=1m --timeout=5s --retries=3 CMD /usr/bin/timeout 5s /bin/sh -c "/usr/bin/wg show | /bin/grep -q interface || exit 1" 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"
@@ -32,12 +40,14 @@ COPY --from=build /app/.output /app
# Copy migrations # Copy migrations
COPY --from=build /app/server/database/migrations /app/server/database/migrations COPY --from=build /app/server/database/migrations /app/server/database/migrations
# libsql (https://github.com/nitrojs/nitro/issues/3328) # libsql (https://github.com/nitrojs/nitro/issues/3328)
RUN cd /app/server && \ COPY --from=build-libsql /app/node_modules /app/server/node_modules
npm install --no-save libsql && \
npm cache clean --force
# cli # cli
COPY --from=build /app/cli/cli.sh /usr/local/bin/cli COPY --from=build /app/cli/cli.sh /usr/local/bin/cli
RUN chmod +x /usr/local/bin/cli RUN chmod +x /usr/local/bin/cli
# Copy amneziawg-go
COPY --from=build /app/amneziawg-go/amneziawg-go /usr/bin/amneziawg-go
RUN chmod +x /usr/bin/amneziawg-go
# Copy amneziawg-tools # Copy amneziawg-tools
COPY --from=build /app/amneziawg-tools/src/wg /usr/bin/awg COPY --from=build /app/amneziawg-tools/src/wg /usr/bin/awg
COPY --from=build /app/amneziawg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick COPY --from=build /app/amneziawg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
@@ -52,7 +62,9 @@ RUN apk add --no-cache \
nftables \ nftables \
kmod \ kmod \
iptables-legacy \ iptables-legacy \
wireguard-tools wireguard-go \
wireguard-tools && \
sed -i 's|\[\[ $proto == -4 \]\] && cmd sysctl -q net\.ipv4\.conf\.all\.src_valid_mark=1|[[ $proto == -4 ]] \&\& [[ $(sysctl -n net.ipv4.conf.all.src_valid_mark) != 1 ]] \&\& cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|' /usr/bin/wg-quick
RUN mkdir -p /etc/amnezia RUN mkdir -p /etc/amnezia
RUN ln -s /etc/wireguard /etc/amnezia/amneziawg RUN ln -s /etc/wireguard /etc/amnezia/amneziawg
@@ -62,7 +74,7 @@ RUN update-alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables
RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save 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,Database,CMD ENV DEBUG=Server,WireGuard,Database,CMD,Firewall
ENV PORT=51821 ENV PORT=51821
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV INSECURE=false ENV INSECURE=false
+4 -3
View File
@@ -1,4 +1,4 @@
FROM docker.io/library/node:jod-alpine FROM docker.io/library/node:krypton-alpine
WORKDIR /app WORKDIR /app
# update corepack # update corepack
@@ -16,6 +16,7 @@ RUN apk add --no-cache \
ip6tables \ ip6tables \
kmod \ kmod \
iptables-legacy \ iptables-legacy \
wireguard-go \
wireguard-tools wireguard-tools
# Use iptables-legacy # Use iptables-legacy
@@ -23,7 +24,7 @@ RUN update-alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables
RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save 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,Database,CMD ENV DEBUG=Server,WireGuard,Database,CMD,Firewall
ENV PORT=51821 ENV PORT=51821
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV INSECURE=true ENV INSECURE=true
@@ -31,7 +32,7 @@ ENV INIT_ENABLED=false
ENV DISABLE_IPV6=false ENV DISABLE_IPV6=false
# Install Dependencies # Install Dependencies
COPY src/package.json src/pnpm-lock.yaml ./ COPY src/package.json src/pnpm-lock.yaml src/pnpm-workspace.yaml ./
RUN pnpm install RUN pnpm install
# Copy Project # Copy Project
+3 -4
View File
@@ -1,16 +1,14 @@
# WireGuard Easy # WireGuard Easy
[![Build & Publish latest Image](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml/badge.svg?branch=production)](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml) [![Build & Publish latest Image](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml/badge.svg)](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml)
[![Lint](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml) [![Lint](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml)
[![GitHub Stars](https://img.shields.io/github/stars/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/stargazers) [![GitHub Stars](https://img.shields.io/github/stars/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/stargazers)
[![License](https://img.shields.io/github/license/wg-easy/wg-easy)](LICENSE) [![License](https://img.shields.io/github/license/wg-easy/wg-easy)](LICENSE)
[![GitHub Release](https://img.shields.io/github/v/release/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/releases/latest) [![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) [![Image Pulls](https://img.shields.io/badge/image_pulls-28M+-blue)](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy)
You have found the easiest way to install & manage WireGuard on any Linux host! 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" alt="wg-easy Screenshot" /> <img src="./assets/screenshot.png" width="802" alt="wg-easy Screenshot" />
</p> </p>
@@ -33,6 +31,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
- IPv6 support - IPv6 support
- CIDR support - CIDR support
- 2FA support - 2FA support
- Per-client firewall filtering (requires iptables)
> [!NOTE] > [!NOTE]
> To better manage documentation for this project, it has its own site here: [https://wg-easy.github.io/wg-easy/latest](https://wg-easy.github.io/wg-easy/latest) > To better manage documentation for this project, it has its own site here: [https://wg-easy.github.io/wg-easy/latest](https://wg-easy.github.io/wg-easy/latest)
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

@@ -0,0 +1,63 @@
{
"fill" : {
"automatic-gradient" : "display-p3:0.48853,0.13220,0.12335,1.00000"
},
"groups" : [
{
"layers" : [
{
"fill" : {
"automatic-gradient" : "srgb:1.00000,1.00000,1.00000,1.00000"
},
"image-name" : "wireguard-logo.png",
"name" : "wireguard-logo",
"position" : {
"scale" : 0.5,
"translation-in-points" : [
255.828125,
-225.5
]
}
},
{
"fill-specializations" : [
{
"value" : {
"automatic-gradient" : "srgb:1.00000,1.00000,1.00000,1.00000"
}
},
{
"appearance" : "dark",
"value" : {
"automatic-gradient" : "display-p3:0.48853,0.13220,0.12335,1.00000"
}
}
],
"image-name" : "ticket.png",
"name" : "ticket",
"position" : {
"scale" : 1.2,
"translation-in-points" : [
-119.91562499999998,
165.65625
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 167 KiB

+19 -10
View File
@@ -2,17 +2,15 @@
First and foremost, thank you! We appreciate that you want to contribute to wg-easy, your time is valuable, and your contributions mean a lot to us. First and foremost, thank you! We appreciate that you want to contribute to wg-easy, your time is valuable, and your contributions mean a lot to us.
## Important! ## Important!
By contributing to this project, you: By contributing to this project, you:
* Agree that you have authored 100% of the content - Agree that you have authored 100% of the content
* Agree that you have the necessary rights to the content - Agree that you have the necessary rights to the content
* Agree that you have received the necessary permissions from your employer to make the contributions (if applicable) - Agree that you have received the necessary permissions from your employer to make the contributions (if applicable)
* Agree that the content you contribute may be provided under the Project license(s) - Agree that the content you contribute may be provided under the Project license(s)
* Agree that, if you did not author 100% of the content, the appropriate licenses and copyrights have been added along with any other necessary attribution. - Agree that, if you did not author 100% of the content, the appropriate licenses and copyrights have been added along with any other necessary attribution.
## Getting started ## Getting started
@@ -24,7 +22,6 @@ Creating an issue is the simplest form of contributing to a project. But there a
- Feature requests - Feature requests
- Bug reports - Bug reports
## Showing support for wg-easy ## Showing support for wg-easy
Please keep in mind that open source software is built by people like you, who spend their free time creating things the rest the community can use. Please keep in mind that open source software is built by people like you, who spend their free time creating things the rest the community can use.
@@ -34,6 +31,20 @@ Don't have time to contribute? No worries, here are some other ways to show your
- star the [project](https://github.com/wg-easy/wg-easy) - star the [project](https://github.com/wg-easy/wg-easy)
- tweet your support for wg-easy - tweet your support for wg-easy
## AI Contribution Policy
Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly and disclose that use clearly.
1. Review AI-generated code closely before marking a pull request ready for review.
2. Manually test the changes and add appropriate automated tests where feasible.
3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review.
4. Disclose AI-assisted content clearly.
5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit.
Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves.
We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process.
## Issues ## Issues
@@ -71,7 +82,6 @@ Please be as descriptive as possible when creating an issue. Give us the informa
- **error messages**: (required) please paste any error messages into the issue, or a [gist](https://gist.github.com/) - **error messages**: (required) please paste any error messages into the issue, or a [gist](https://gist.github.com/)
- **extensions, plugins, helpers, etc** (if applicable): please list any extensions you're using - **extensions, plugins, helpers, etc** (if applicable): please list any extensions you're using
### Closing issues ### Closing issues
The original poster or the maintainers of wg-easy may close an issue at any time. Typically, but not exclusively, issues are closed when: The original poster or the maintainers of wg-easy may close an issue at any time. Typically, but not exclusively, issues are closed when:
@@ -81,7 +91,6 @@ The original poster or the maintainers of wg-easy may close an issue at any time
- An issue is clearly a duplicate of another issue, in which case the duplicate issue will be linked. - An issue is clearly a duplicate of another issue, in which case the duplicate issue will be linked.
- A discussion has clearly run its course - A discussion has clearly run its course
## Next steps ## Next steps
**Tips for creating idiomatic issues** **Tips for creating idiomatic issues**
+1
View File
@@ -5,6 +5,7 @@ title: API
/// warning | Breaking Changes /// 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. This API is not yet stable and may change in the future. The API is currently in development and is subject to change without notice. The API is not yet documented, but we will add documentation as the API stabilizes.
/// ///
You can use the API to interact with the application programmatically. The API is available at `/api` and supports both GET and POST requests. The API is designed to be simple and easy to use, with a focus on providing a consistent interface for all endpoints. You can use the API to interact with the application programmatically. The API is available at `/api` and supports both GET and POST requests. The API is designed to be simple and easy to use, with a focus on providing a consistent interface for all endpoints.
+47 -5
View File
@@ -2,7 +2,9 @@
title: AmneziaWG title: AmneziaWG
--- ---
Experimental support for AmneziaWG can be enabled by setting the `EXPERIMENTAL_AWG` environment variable to `true`. This feature is still under development and may change in future releases. ## Introduction
**AmneziaWG** is a modified version of the WireGuard protocol with enhanced traffic obfuscation capabilities. AmneziaWG's primary goal is to counter deep packet inspection (DPI) systems and bypass VPN blocking.
AmneziaWG adds multi-level transport-layer obfuscation by: AmneziaWG adds multi-level transport-layer obfuscation by:
@@ -12,6 +14,12 @@ AmneziaWG adds multi-level transport-layer obfuscation by:
These measures make it harder for third parties to analyze or identify your traffic, enhancing both privacy and security. These measures make it harder for third parties to analyze or identify your traffic, enhancing both privacy and security.
## Activating AmneziaWG
You must install the [AmneziaWG kernel module](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module) on the host system.
Experimental support for AmneziaWG can be enabled by setting the `EXPERIMENTAL_AWG` environment variable to `true`. Starting from wg-easy version 16, this setting will be enabled by default. This feature is still under development and may change in future releases.
When enabled, wg-easy will automatically detect whether the AmneziaWG kernel module is available. If it is not, the system will fall back to the standard WireGuard module. When enabled, wg-easy will automatically detect whether the AmneziaWG kernel module is available. If it is not, the system will fall back to the standard WireGuard module.
To override this automatic detection, set the `OVERRIDE_AUTO_AWG` environment variable. By default, this variable is unset. To override this automatic detection, set the `OVERRIDE_AUTO_AWG` environment variable. By default, this variable is unset.
@@ -21,17 +29,51 @@ Possible values:
- `awg` — Force use of AmneziaWG - `awg` — Force use of AmneziaWG
- `wg` — Force use of standard WireGuard - `wg` — Force use of standard WireGuard
To be able to connect to wg-easy if AmneziaWG is enabled, you must have a AmneziaWG-compatible client. ## AmneziaWG Parameters
Parameter descriptions can be found in the [AmneziaWG documentation](https://docs.amnezia.org/documentation/amnezia-wg) and on the [kernel module page](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module).
All parameters except I1-I5 will be set at first startup. For information on how to set I1-I5 parameters, refer to the [AmneziaWG documentation](https://docs.amnezia.org/documentation/instructions/new-amneziawg-selfhosted/#how-to-extract-a-protocol-signature-for-amneziawg-15-manually).
If a parameter is not set, it will not be added to the configuration. If all AmneziaWG-specific parameters are absent, AmneziaWG will be fully compatible with standard WireGuard.
### Parameter Compatibility Table
| Parameter | Can differ between server and client | Configurable on server | Configurable on client |
| --------- | ------------------------------------ | ---------------------- | ------------------------ |
| Jc | :white_check_mark: Yes | :white_check_mark: | :white_check_mark: |
| Jmin | :white_check_mark: Yes | :white_check_mark: | :white_check_mark: |
| Jmax | :white_check_mark: Yes | :white_check_mark: | :white_check_mark: |
| S1-S4 | :x: No, must match | :white_check_mark: | :x: (copied from server) |
| H1-H4 | :x: No, must match | :white_check_mark: | :x: (copied from server) |
| I1-I5 | :white_check_mark: Yes | :white_check_mark: | :white_check_mark: |
## Client Applications
To be able to connect to wg-easy if AmneziaWG is enabled, you must have an AmneziaWG-compatible client. Where an AmneziaWG app is available for your platform, it is recommended to use it rather than Amnezia VPN.
Android: Android:
- [AmneziaWG](https://play.google.com/store/apps/details?id=org.amnezia.awg) - Official Client - [AmneziaWG](https://play.google.com/store/apps/details?id=org.amnezia.awg) - AmneziaWG Official Client
- [WG Tunnel](https://play.google.com/store/apps/details?id=com.zaneschepke.wireguardautotunnel) - Third Party Client - [WG Tunnel](https://play.google.com/store/apps/details?id=com.zaneschepke.wireguardautotunnel) - Third Party Client
- [Amnezia VPN](https://play.google.com/store/apps/details?id=org.amnezia.vpn) - Amnezia VPN Official Client
iOS and macOS: iOS and macOS:
- [AmneziaWG](https://apps.apple.com/us/app/amneziawg/id6478942365) - Official Client - [AmneziaWG](https://apps.apple.com/us/app/amneziawg/id6478942365) - AmneziaWG Official Client
- [Amnezia VPN](https://apps.apple.com/us/app/amneziavpn/id1600529900) - Amnezia VPN Official Client
Windows: Windows:
- [AmneziaWG](https://github.com/amnezia-vpn/amneziawg-windows-client/releases) - Official Client - [AmneziaWG](https://github.com/amnezia-vpn/amneziawg-windows-client/releases) - AmneziaWG Official Client (Requires building from source code)
- [Amnezia VPN](https://amnezia.org/downloads) - Amnezia VPN Official Client
Linux:
- [Amnezia VPN](https://amnezia.org/downloads) - Amnezia VPN Official Client
- [amneziawg-tools](https://github.com/amnezia-vpn/amneziawg-tools) - AmneziaWG Tools
OpenWRT:
- [AmneziaWG OpenWRT](https://github.com/Slava-Shchipunov/awg-openwrt) - AmneziaWG OpenWRT Packages
- [AmneziaWG OpenWRT](https://github.com/lolo6oT/awg-openwrt) - AmneziaWG OpenWRT Packages
@@ -0,0 +1,182 @@
---
title: External Authentication
---
## OAuth
### Setup
To enable OAuth set the env var `OAUTH_PROVIDERS` to any of the following providers:
| Provider | Value |
| ----------------------------- | -------- |
| [Google](#google) | `google` |
| [GitHub](#github) | `github` |
| [Generic OIDC](#generic-oidc) | `oidc` |
You can enable multiple providers by separating them with a comma:
e.g. `google,github`
### Auto Register
To automatically register users that log in with an OAuth provider, set the following environment variable to `true`:
| Env | Required | Default | Description |
| --------------------- | -------- | ------- | ------------------------ |
| `OAUTH_AUTO_REGISTER` | :x: | `false` | Enable auto-registration |
When enabled:
- If a user logs in with an email address that is not yet registered, a new account will be created for them.
- If a user logs in with an email address that is already registered, their account will be linked to the OAuth provider (if not already linked), regardless of the value of `OAUTH_AUTO_REGISTER`.
/// warning | Security
Users will be created with Admin Permissions, as the permissions system is not yet implemented. Only enable this if you trust all users that can log in with the OAuth provider.
Use [Allowed Domains](#allowed-domains) to restrict which users can log in.
///
### Allowed Domains
To only allow users with an email address from a specific domain to log in, set the following environment variable to the allowed domain.
| Env | Required | Default | Description |
| ----------------------- | -------- | ------- | --------------------- |
| `OAUTH_ALLOWED_DOMAINS` | :x: | - | Allowed email domains |
You can allow multiple domains by separating them with a comma:
e.g. `example.com,example.org`
### Auto Launch
To automatically launch the OAuth login flow when visiting the login page, set the following environment variable to the provider you want to launch:
| Env | Required | Default | Description |
| ------------------- | -------- | ------- | ----------------------------- |
| `OAUTH_AUTO_LAUNCH` | :x: | - | Auto launch an OAuth provider |
When enabled:
- Visiting the login page will automatically redirect to the selected provider's login page
- The user can still access the normal login page by visiting `/login?auto_launch=false`
- You can auto launch any provider by visiting `/login?auto_launch=<provider>`
### Redirect URIs
You have to configure the following redirect URIs in your OAuth provider:
- `https://<your-domain>/api/auth/<provider>/callback`
Used to log in to with the provider
- `https://<your-domain>/api/auth/<provider>/link`
Used to link an existing account to the provider
If your provider does not support multiple redirect URIs (e.g. GitHub) but allows multiple URIs under the same base, then configure:
- `https://<your-domain>/api/auth/<provider>/`
### Provider Configuration
#### Google
| Env | Required | Description |
| ---------------------------- | ------------------ | -------------------- |
| `OAUTH_GOOGLE_CLIENT_ID` | :white_check_mark: | Google Client ID |
| `OAUTH_GOOGLE_CLIENT_SECRET` | :white_check_mark: | Google Client Secret |
<h5>Setup</h5>
1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
2. Create an OAuth 2.0 Client ID (Web application)
3. Add Authorized redirect URI: See [Redirect URIs](#redirect-uris)
4. Copy the Client ID and Client Secret to the environment variables
#### GitHub
| Env | Required | Description |
| ---------------------------- | ------------------ | -------------------- |
| `OAUTH_GITHUB_CLIENT_ID` | :white_check_mark: | GitHub Client ID |
| `OAUTH_GITHUB_CLIENT_SECRET` | :white_check_mark: | GitHub Client Secret |
<h5>Setup</h5>
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
2. Create a new OAuth App
3. Add Authorization callback URL: See [Redirect URIs](#redirect-uris)
4. Create a new client secret
5. Copy the Client ID and Client Secret to the environment variables
#### Generic OIDC
This supports generic OIDC providers like Authelia, Authentik, etc.
The provider needs to support:
- PKCE
- default scopes: `openid email profile`
- Client Secret Authentication `client_secret_post`
The provider needs to be available with HTTPS and have a valid certificate.
| Env | Required | Default | Example | Description |
| -------------------------- | ------------------ | ------- | -------------------------- | ------------------ |
| `OAUTH_OIDC_SERVER` | :white_check_mark: | - | `https://auth.example.com` | OIDC Server |
| `OAUTH_OIDC_CLIENT_ID` | :white_check_mark: | - | - | OIDC Client ID |
| `OAUTH_OIDC_CLIENT_SECRET` | :white_check_mark: | - | - | OIDC Client Secret |
| `OAUTH_OIDC_NAME` | :x: | OIDC | `Authelia` | Provider Name |
##### Authelia Setup
Generate Client ID and Secret:
```shell
# Client ID
docker run --rm authelia/authelia:latest authelia crypto rand --length 72 --charset rfc3986
# Client Secret
docker run --rm authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986
```
```yaml
- client_id: '...'
client_name: wg-easy
client_secret: '$pbkdf2-...'
redirect_uris:
- https://<your-domain>/api/auth/oidc/callback
- https://<your-domain>/api/auth/oidc/link
scopes:
- openid
- profile
- email
authorization_policy: one_factor
pre_configured_consent_duration: 1 week
require_pkce: true
token_endpoint_auth_method: client_secret_post
```
#### Generic OAuth
Not currently supported
### Disable Password Authentication
To disable password-based authentication and only allow login via OAuth providers, set the following environment variable to `true`:
| Env | Required | Default | Description |
| ----------------------- | -------- | ------- | ------------------------------- |
| `DISABLE_PASSWORD_AUTH` | :x: | `false` | Disable password authentication |
When enabled:
- Users will not be able to log in with a password
/// warning | Access Recovery
Before disabling password authentication, ensure that at least one OAuth provider is configured and that you have successfully linked an administrator account.
If no login method is available, you will not be able to log in to the application and will need to reset the configuration to regain access.
///
@@ -5,11 +5,12 @@ title: Optional Configuration
You can set these environment variables to configure the container. They are not required, but can be useful in some cases. You can set these environment variables to configure the container. They are not required, but can be useful in some cases.
| Env | Default | Example | Description | | Env | Default | Example | Description |
| -------------- | --------- | ----------- | ---------------------------------- | | ----------------------- | --------- | ----------- | --------------------------------------- |
| `PORT` | `51821` | `6789` | TCP port for Web UI. | | `PORT` | `51821` | `6789` | TCP port for Web UI. |
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. | | `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
| `INSECURE` | `false` | `true` | If access over http is allowed | | `INSECURE` | `false` | `true` | If access over http is allowed |
| `DISABLE_IPV6` | `false` | `true` | If IPv6 support should be disabled | | `DISABLE_IPV6` | `false` | `true` | If IPv6 support should be disabled |
| `DISABLE_VERSION_CHECK` | `false` | `true` | If wg-easy should check for new updates |
/// note | IPv6 Caveats /// note | IPv6 Caveats
@@ -20,74 +21,3 @@ You will however still see a IPv6 address in the Web UI, but it won't be used.
This option can be removed in the future, as more devices support IPv6. This option can be removed in the future, as more devices support IPv6.
/// ///
## Configuration Overrides
These environment variables allow you to override settings that would normally be configured through the Admin Panel. When set, these values take precedence over database settings at runtime.
### Interface Settings
| Env | Example | Description |
| -------------- | ------------- | ------------------------- |
| `WG_PORT` | `51820` | WireGuard interface port |
| `WG_DEVICE` | `eth0` | Network device/interface |
| `WG_MTU` | `1420` | Maximum Transmission Unit |
| `WG_IPV4_CIDR` | `10.8.0.0/24` | IPv4 CIDR range |
| `WG_IPV6_CIDR` | `fdcc::/112` | IPv6 CIDR range |
### Client Connection Settings
| Env | Example | Description |
| --------------------------------- | ----------------- | ------------------------------- |
| `WG_HOST` | `vpn.example.com` | Host clients will connect to |
| `WG_CLIENT_PORT` | `51820` | Port clients will connect to |
| `WG_DEFAULT_DNS` | `1.1.1.1,8.8.8.8` | Default DNS servers for clients |
| `WG_DEFAULT_ALLOWED_IPS` | `0.0.0.0/0,::/0` | Default allowed IPs for clients |
| `WG_DEFAULT_MTU` | `1420` | Default MTU for clients |
| `WG_DEFAULT_PERSISTENT_KEEPALIVE` | `25` | Default persistent keepalive |
### General Settings
| Env | Example | Description |
| ----------------------- | ----------------- | ------------------------- |
| `WG_SESSION_TIMEOUT` | `3600` | Session timeout (seconds) |
| `WG_METRICS_PASSWORD` | `mypassword123` | Metrics endpoint password |
| `WG_METRICS_PROMETHEUS` | `true` or `false` | Enable Prometheus metrics |
| `WG_METRICS_JSON` | `true` or `false` | Enable JSON metrics |
### Hooks
| Env | Example | Description |
| -------------- | ------------------------- | --------------------- |
| `WG_PRE_UP` | `echo "Starting WG"` | PreUp hook command |
| `WG_POST_UP` | `iptables -A FORWARD ...` | PostUp hook command |
| `WG_PRE_DOWN` | `echo "Stopping WG"` | PreDown hook command |
| `WG_POST_DOWN` | `iptables -D FORWARD ...` | PostDown hook command |
/// warning | Override Behavior
When these override environment variables are set:
- The specified values will be used at runtime instead of database settings
- You can still update these fields through the Web UI and they will be saved to the database
- However, the overridden values from environment variables will always take precedence at runtime
- The Web UI will display the database values with warning indicators showing which fields are overridden
- On first start, if no database values exist, some overridden values will be saved to the database
Some overrides will not be applied to existing clients until they are manually edited.
- `WG_DEFAULT_*` settings will only apply to new clients
- `WG_IPV4_CIDR` and `WG_IPV6_CIDR` changes will require clients to be manually edited to take effect
///
/// note | Note on Port Variables
- `WG_PORT` - The port WireGuard listens on (interface port)
- `WG_CLIENT_PORT` - The port clients connect to (endpoint port, uses `WG_PORT` if not set)
- `PORT` - The port the Web UI listens on (HTTP server port)
In most cases you will only need to set `WG_PORT` to change the WireGuard port.
Keep in mind that you have to adjust both sides of the port publish option in your docker setup.
///
@@ -11,20 +11,19 @@ These will only be used during the first start of the container. After that, the
| `INIT_ENABLED` | `true` | Enables the below env vars | 0 | | `INIT_ENABLED` | `true` | Enables the below env vars | 0 |
| `INIT_USERNAME` | `admin` | Sets admin username | 1 | | `INIT_USERNAME` | `admin` | Sets admin username | 1 |
| `INIT_PASSWORD` | `Se!ureP%ssw` | Sets admin password | 1 | | `INIT_PASSWORD` | `Se!ureP%ssw` | Sets admin password | 1 |
| `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 2 | | `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 1 |
| `INIT_PORT` | `51820` | Port clients will connect to and WireGuard will listen on | 2 | | `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 | 3 | | `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 | 4 | | `INIT_IPV4_CIDR` | `10.8.0.0/24` | Sets IPv4 cidr | 3 |
| `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 4 | | `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 3 |
| `INIT_ALLOWED_IPS` | `10.8.0.0/24,2001:0DB8::/32` | Sets global Allowed IPs | 5 | | `INIT_ALLOWED_IPS` | `10.8.0.0/24,2001:0DB8::/32` | Sets global Allowed IPs | 4 |
/// warning | Variables have to be used together /// warning | Variables have to be used together
If variables are in the same group, you have to set all of them. For example, if you set `INIT_IPV4_CIDR`, you also have to set `INIT_IPV6_CIDR`. If 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`.
To skip the setup process, you must configure groups `1` and `2`. You can alternatively use `WG_HOST` and `WG_PORT` to set group `2` without using the `INIT_` variables. If you want to skip the setup process, you have to configure group `1`
Avoid setting both `INIT_` and `WG_` variables for the same setting to prevent confusion.
/// ///
/// note | Security /// note | Security
@@ -32,4 +31,5 @@ Avoid setting both `INIT_` and `WG_` variables for the same setting to prevent c
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. 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. It's recommended to remove the variables after the setup is done to prevent the password from being exposed.
/// ///
@@ -39,4 +39,5 @@ You can use the following Grafana dashboard to visualize the metrics:
The Grafana dashboard is not official and is not maintained by the `wg-easy` team. If you have any issues with the dashboard, please contact the author of the dashboard. 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. See [#1299](https://github.com/wg-easy/wg-easy/pull/1299) for more information.
/// ///
@@ -7,7 +7,7 @@ This guide will help you migrate from `v14` to version `v15` of `wg-easy`.
## Changes ## Changes
- This is a complete rewrite of the `wg-easy` project, therefore the configuration files and the way you interact with the project have changed. - This is a complete rewrite of the `wg-easy` project, therefore the configuration files and the way you interact with the project have changed.
- If you use armv6, you unfortunately won't be able to migrate to `v15`. - If you use armv6 or armv7, you unfortunately won't be able to migrate to `v15`.
- If you are connecting to the Web UI via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container. - If you are connecting to the Web UI via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container.
## Migration ## Migration
@@ -51,9 +51,7 @@ In the setup wizard, select that you already have a configuration file and uploa
### Environment Variables ### Environment Variables
v15 does use some of the environment variables as v14. View [Configuration Overrides](../config/optional-config.md#configuration-overrides) to see which environment variables are supported in v15. v15 does not use the same environment variables as v14, most of them have been moved to the Admin Panel in the Web UI.
If you want to be able to change settings through the Web UI, do not set the corresponding environment variables, as they will override the database settings. Instead, manually change the settings through the Web UI after the migration.
### Done ### Done
@@ -9,6 +9,7 @@ This project is Open Source. That means that you can contribute on enhancements,
/// note | Attention /// 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! **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. When opening an issue, please provide details use case to let the community reproduce your problem.
@@ -16,6 +17,7 @@ When opening an issue, please provide details use case to let the community repr
/// note | Attention /// note | Attention
**Use the issue templates** to provide the necessary information. Issues which do not use these templates are not worked on and closed. **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. By raising issues, I agree to these terms and I understand, that the rules set for the issue tracker will help both maintainers as well as everyone to find a solution.
@@ -39,6 +41,7 @@ When an option is marked with "not officially supported" / "unsupported", then s
/// question | Motivation /// 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. 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 ### Submit a Pull-Request
+5 -1
View File
@@ -9,9 +9,11 @@ This tutorial is a follow-up to the official [Traefik tutorial](./traefik.md). I
- A working [wg-easy](./basic-installation.md) and [Traefik](./traefik.md) setup from the previous guides. - A working [wg-easy](./basic-installation.md) and [Traefik](./traefik.md) setup from the previous guides.
/// warning | Important: Following this guide will reset your WireGuard configuration. /// warning | Important: Following this guide will reset your WireGuard configuration.
The process involves re-creating the `wg-easy` container and its data, which means **all existing WireGuard clients and settings will be deleted.** The process involves re-creating the `wg-easy` container and its data, which means **all existing WireGuard clients and settings will be deleted.**
You will need to create your clients again after completing this guide. You will need to create your clients again after completing this guide.
/// ///
## Add `adguard` configuration ## Add `adguard` configuration
@@ -152,13 +154,15 @@ networks:
2. Navigate to `https://adguard.$example.com$` to begin the AdGuard Home setup. 2. Navigate to `https://adguard.$example.com$` to begin the AdGuard Home setup.
/// warning | Important: Configure AdGuard Home Admin Web Interface Port /// warning | Important: Configure AdGuard Home Admin Web Interface Port
During the initial AdGuard Home setup on the `Step 2/5` page, you **must** set the **Admin Web Interface Port** to **3000**. Do not use the default port 80, as it will not work with the Traefik configuration. During the initial AdGuard Home setup on the `Step 2/5` page, you **must** set the **Admin Web Interface Port** to **3000**. Do not use the default port 80, as it will not work with the Traefik configuration.
After completing the setup, the AdGuard UI might appear unresponsive. This is expected. **Simply reload the page**, and the panel will display correctly. After completing the setup, the AdGuard UI might appear unresponsive. This is expected. **Simply reload the page**, and the panel will display correctly.
///
> If you accidentally left it default (80), you will need to manually edit the `docker-compose.yml` file for AdGuard Home (`/etc/docker/containers/adguard/docker-compose.yml`) and change the line `traefik.http.services.adguard.loadbalancer.server.port=3000` to `traefik.http.services.adguard.loadbalancer.server.port=80`. After making this change, restart AdGuard Home by navigating to `/etc/docker/containers/adguard` and running `sudo docker compose up -d`. > If you accidentally left it default (80), you will need to manually edit the `docker-compose.yml` file for AdGuard Home (`/etc/docker/containers/adguard/docker-compose.yml`) and change the line `traefik.http.services.adguard.loadbalancer.server.port=3000` to `traefik.http.services.adguard.loadbalancer.server.port=80`. After making this change, restart AdGuard Home by navigating to `/etc/docker/containers/adguard` and running `sudo docker compose up -d`.
///
## Final System Checks ## Final System Checks
### Firewall ### Firewall
@@ -20,7 +20,7 @@ File: `/etc/docker/containers/watchtower/docker-compose.yml`
```yaml ```yaml
services: services:
watchtower: watchtower:
image: containrrr/watchtower:latest image: nickfedor/watchtower:latest
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
env_file: env_file:
@@ -2,13 +2,13 @@
title: Basic Installation title: Basic Installation
--- ---
<!-- TOOD: add docs for pihole, nginx, caddy, traefik --> <!-- TODO: add docs for pihole, nginx, caddy, traefik -->
## Requirements ## Requirements
1. You need to have a host that you can manage 1. You need to have a host that you can manage
2. You need to have a domain name or a public IP address 2. You need to have a domain name or a public IP address
3. You need a supported architecture (x86_64, arm64, armv7) 3. You need a supported architecture (x86_64, arm64)
4. You need curl installed on your host 4. You need curl installed on your host
## Install Docker ## Install Docker
+1
View File
@@ -5,6 +5,7 @@ title: Caddy
/// note | Opinionated /// note | Opinionated
This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths. This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths.
/// ///
We're using [Caddy](https://caddyserver.com/) here as reverse proxy to serve `wg-easy` on [https://wg-easy.example.com](https://wg-easy.example.com) via TLS. We're using [Caddy](https://caddyserver.com/) here as reverse proxy to serve `wg-easy` on [https://wg-easy.example.com](https://wg-easy.example.com) via TLS.
@@ -7,6 +7,7 @@ title: No Reverse Proxy
This is insecure. You should use a reverse proxy to secure the connection. This is insecure. You should use a reverse proxy to secure the connection.
Only use this method if you know what you are doing. 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. 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.
+17
View File
@@ -61,6 +61,7 @@ Pick an IPv4 and IPv6 subnet for your clients and add static routes on your rout
/// note | 2001:db8::/32 /// note | 2001:db8::/32
The _documentation prefix_ `2001:db8::/32` (RFC 3849) used in this example is not meant for production use, replace it with your own ISP-assigned IPv6 prefix (GUA) or local prefix (ULA) The _documentation prefix_ `2001:db8::/32` (RFC 3849) used in this example is not meant for production use, replace it with your own ISP-assigned IPv6 prefix (GUA) or local prefix (ULA)
/// ///
I want my WireGuard clients in `192.168.0.0/24` and `2001:db8:abc:0::/64`. I want my WireGuard clients in `192.168.0.0/24` and `2001:db8:abc:0::/64`.
@@ -93,3 +94,19 @@ PostDown
```shell ```shell
iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT
``` ```
/// warning | Important: When using nftables use the following hooks instead.
PostUp
```shell
nft add chain ip filter WG_EASY; nft add rule ip filter DOCKER-USER jump WG_EASY; nft add rule ip filter WG_EASY iifname {{device}} accept; nft add rule ip filter WG_EASY oifname {{device}} accept; nft add chain ip6 filter WG_EASY; nft add rule ip6 filter DOCKER-USER jump WG_EASY; nft add rule ip6 filter WG_EASY iifname {{device}} accept; nft add rule ip6 filter WG_EASY oifname {{device}} accept;
```
PostDown
```shell
nft delete rule ip filter DOCKER-USER handle $(nft -a list chain ip filter DOCKER-USER | awk '/jump WG_EASY/ {print $NF}'); nft flush chain ip filter WG_EASY; nft delete chain ip filter WG_EASY; nft delete rule ip6 filter DOCKER-USER handle $(nft -a list chain ip6 filter DOCKER-USER | awk '/jump WG_EASY/ {print $NF}'); nft flush chain ip6 filter WG_EASY; nft delete chain ip6 filter WG_EASY
```
///
@@ -5,6 +5,7 @@ title: Traefik
/// note | Opinionated /// note | Opinionated
This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths. This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths.
/// ///
## Create docker compose project ## Create docker compose project
+42
View File
@@ -6,6 +6,20 @@ hide:
Here are some frequently asked questions or errors about `wg-easy`. If you have a question that is not answered here, please feel free to open a discussion on GitHub. Here are some frequently asked questions or errors about `wg-easy`. If you have a question that is not answered here, please feel free to open a discussion on GitHub.
## How do I restrict client access to specific networks or servers?
Use the **Per-Client Firewall** feature to enforce server-side restrictions on what each client can access.
**Requirements:** This feature requires `iptables` (and `ip6tables` for IPv6) to be installed on the host system.
1. Enable "Per-Client Firewall" in **Admin Panel → Interface**
2. Edit a client and configure "Firewall Allowed IPs"
3. Specify which destinations the client should be allowed to access
Unlike "Allowed IPs" which only controls client-side routing, firewall rules are enforced by the server and cannot be bypassed.
See the [Admin Panel Guide](./guides/admin.md#per-client-firewall) and [Client Guide](./guides/clients.md#firewall-allowed-ips) for detailed configuration.
## Error: WireGuard exited with the error: Cannot find device "wg0" ## 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. This error indicates that the WireGuard interface `wg0` does not exist. This can happen if the WireGuard kernel module is not loaded or if the interface was not created properly.
@@ -95,3 +109,31 @@ To resolve this issue, you can try the following steps:
```shell ```shell
echo "ip6table_filter" | sudo tee -a /etc/modules echo "ip6table_filter" | sudo tee -a /etc/modules
``` ```
## Clients lose connectivity after restarting the container when using multiple networks?
When you attach multiple Docker networks (e.g., `wg` and a reverse proxy network like `traefik` or `nginx`) to the `wg-easy` container, Docker might assign the network interfaces randomly (e.g., swapping `eth0` and `eth1`). Since `wg-easy` expects the wireguard interface to act as `eth0` and configures `POSTROUTING` rules for it, connectivity will break if the interfaces are swapped upon container restart.
To solve this, specify the `interface_name` and `gw_priority` explicitly in your `docker-compose.yml` file to guarantee that the `wg` network always binds to `eth0` and acts as the default gateway.
**Example `docker-compose.yml`:**
```yaml
services:
wg-easy:
# ... other configuration ...
networks:
wg:
interface_name: eth0
gw_priority: 1
ipv4_address: 10.42.42.42
nginx:
interface_name: eth1
gw_priority: 0
networks:
wg:
# ... wg network config ...
nginx:
external: true
```
+5 -3
View File
@@ -12,7 +12,7 @@ Before you can get started with deploying your own VPN, there are some requireme
1. You need to have a host that you can manage 1. You need to have a host that you can manage
2. You need to have a domain name or a public IP address 2. You need to have a domain name or a public IP address
3. You need a supported architecture (x86_64, arm64, armv7) 3. You need a supported architecture (x86_64, arm64)
### Host Setup ### Host Setup
@@ -24,6 +24,7 @@ There are a few requirements for a suitable host system:
On the host, you need to have a suitable container runtime (like _Docker_ or _Podman_) installed. We assume [_Docker Compose_][docker-compose] is [installed][docker-compose-installation]. We have aligned file names and configuration conventions with the latest [Docker Compose specification][docker-compose-specification]. 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]. If you're using podman, make sure to read the related [documentation][docs-podman].
/// ///
[docker-compose]: https://docs.docker.com/compose/ [docker-compose]: https://docs.docker.com/compose/
@@ -45,15 +46,15 @@ All workflows are using the tagging convention listed below. It is subsequently
| tag | Type | Example | Description | | tag | Type | Example | Description |
| ------------- | ------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------- | | ------------- | ------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `15` | latest minor for that major tag | `ghcr.io/wg-easy/wg-easy:15` | latest features for specific major versions, no breaking changes, recommended | | `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` | 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 | | `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 | | `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 | | `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs |
| `latest` | latest tag | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | points to the v14 release, should be avoided |
<!-- ref: major version (check links too) --> <!-- ref: major version (check links too) -->
When publishing a tag we follow the [Semantic Versioning][semver] specification. The `latest` tag is always pointing to the latest stable release. If you want to avoid breaking changes, use the major version tag (e.g. `15`). When publishing a tag we follow the [Semantic Versioning][semver] specification. Pin to the latest major version to avoid breaking changes (e.g. `15`), avoid using the `latest` tag.
[github-ci]: https://github.com/wg-easy/wg-easy/actions [github-ci]: https://github.com/wg-easy/wg-easy/actions
[ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy [ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy
@@ -69,6 +70,7 @@ When publishing a tag we follow the [Semantic Versioning][semver] specification.
/// danger | Use the Correct Commands For Stopping and Starting `wg-easy` /// 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. **Use `sudo docker compose up / down`, not `sudo docker compose start / stop`**. Otherwise, the container is not properly destroyed and you may experience problems during startup because of inconsistent state.
/// ///
**That's it! It really is that easy**. **That's it! It really is that easy**.
+41 -1
View File
@@ -2,4 +2,44 @@
title: Admin Panel title: Admin Panel
--- ---
TODO ## Interface Settings
### Per-Client Firewall
Enable server-side firewall filtering to enforce network access restrictions per client.
When enabled, each client can have custom "Firewall Allowed IPs" configured that restrict which destinations they can access through the VPN. These restrictions are enforced by the server using iptables/ip6tables and cannot be bypassed by the client.
/// warning | Experimental Feature
This feature is currently experimental. While functional, it should be thoroughly tested in your environment before relying on it for production security requirements. Always verify that firewall rules are working as expected using test traffic or by manually inspecting the rules.
///
**Requirements:**
- `iptables` must be installed on the host system
- `ip6tables` must be installed if IPv6 is enabled (default)
- The feature cannot be enabled if these tools are not available
/// note
Most Linux distributions include iptables by default. If you're running in a minimal container environment, you may need to install the `iptables` package on the host system.
///
**Enable this feature if you want to:**
- Restrict certain clients to only access specific servers or networks
- Prevent clients from accessing the internet while allowing LAN access
- Enforce port-based restrictions (e.g., only allow HTTP/HTTPS)
- Separate routing configuration from security enforcement
**How it works:**
1. Enable "Per-Client Firewall" in Admin Panel → Interface
2. Edit any client to see the new "Firewall Allowed IPs" field
3. Specify allowed destinations (IPs, subnets, ports) for that client
4. Server enforces these rules automatically
See [Edit Client → Firewall Allowed IPs](./clients.md#firewall-allowed-ips) for detailed configuration syntax and examples.
+28
View File
@@ -41,3 +41,31 @@ docker compose exec -it wg-easy cli db:admin:reset --password <new_password>
``` ```
This will reset the password for the admin user to the new password you provided. If you include special characters in the password, make sure to escape them properly. This will reset the password for the admin user to the new password you provided. If you include special characters in the password, make sure to escape them properly.
### Show Clients
List all clients that are currently configured with details such as client ID, Name, Public Key, and enabled status.
```shell
cli clients:list
```
### Show Client QR Code
Display the QR code for a specific client, which can be scanned by a compatible app to import the client's configuration.
```shell
cli clients:qr <client_id>
```
Replace `<client_id>` with the actual client ID you want to show the QR code for.
/// warning | IPv6 Support
IPv6 support is enabled by default, even if you disabled it using environment variables. To disable it pass the `--no-ipv6` flag when running the CLI.
```shell
cli clients:qr <client_id> --no-ipv6
```
///
+54 -1
View File
@@ -19,7 +19,60 @@ Which IPs will be routed through the VPN.
This will not prevent the user from modifying it locally and accessing IP ranges that they should not be able to access. This will not prevent the user from modifying it locally and accessing IP ranges that they should not be able to access.
Use firewall rules to prevent access to IP ranges that the user should not be able to access. Use the Firewall Allowed IPs feature to prevent access to IP ranges that the user should not be able to access.
## Firewall Allowed IPs
/// note | Attention
This field only appears when **Per-Client Firewall** is enabled in the Admin Panel → Interface settings.
///
Server-side firewall rules that restrict which destinations the client can access, regardless of their local configuration.
Unlike "Allowed IPs" which only controls routing on the client side, these rules are enforced by the server using iptables/ip6tables and cannot be bypassed by the client.
**Supported Formats:**
- `10.10.0.3`, `2001:db8::1` - Allow access to a single IP address
- `10.10.0.0/24`, `2001:db8::/32` - Allow access to an entire subnet
- `192.168.1.5:443` - Allow access to specific port (TCP+UDP)
- `192.168.1.5:443/tcp` - Allow access to specific port (TCP only)
- `192.168.1.5:443/udp` - Allow access to specific port (UDP only)
- `10.10.0.0/24:443` - Allow access to an entire subnet on a specific port (TCP+UDP)
- `10.10.0.0/24:443/tcp` - Allow access to an entire subnet on a specific port (TCP only)
- `10.10.0.0/24:443/udp` - Allow access to an entire subnet on a specific port (UDP only)
- `[2001:db8::1]:443` - IPv6 address with port (brackets required)
- `[2001:db8::/32]:443/tcp` - IPv6 CIDR with port and protocol
/// warning | Invalid Formats
Protocol specifiers (`/tcp` or `/udp`) require a port number. The following formats are **not supported** and will result in an error:
- `10.10.0.3/tcp` (use `10.10.0.3:443/tcp` instead)
- `10.10.0.0/24/udp` (use `10.10.0.0/24:53/udp` instead)
///
**Behavior:**
- **Empty**: Falls back to the client's "Allowed IPs" setting
- **Specified**: Only listed destinations are accessible (allow-only, everything else is blocked)
- **Disable for specific client**: To disable firewall filtering for a single client while keeping it enabled for others, add `0.0.0.0/0, ::/0` to allow all traffic
/// note
To allow clients to reach the VPN server itself (e.g. for DNS), include the server's VPN address in the firewall allowed IPs.
///
**Use Case Examples**:
- Allow only specific servers: `10.10.0.5`
- Allow only internal network: `10.10.0.0/24, 192.168.1.0/24`
- Allow only web browsing: `0.0.0.0/0:80, 0.0.0.0/0:443, [::/0]:80, [::/0]:443`
- Block internet, allow LAN: Leave "Allowed IPs" as `0.0.0.0/0, ::/0` but set Firewall IPs to `10.0.0.0/8, 192.168.0.0/16`
## Server Allowed IPs ## Server Allowed IPs
+2 -1
View File
@@ -8,7 +8,8 @@ hide:
/// info | This Documentation is Versioned /// info | This Documentation is Versioned
**Make sure** to select the correct version of this documentation! It should match the version of the image you are using. The default version corresponds to the `:latest` image tag - [the most recent stable release][docs-tagging]. **Make sure** to select the correct version of this documentation! It should match the version of the image you are using. The default version corresponds to [the most recent stable release][docs-tagging].
/// ///
This documentation provides you not only with the basic setup and configuration of `wg-easy` but also with advanced configuration, elaborate usage scenarios, detailed examples, hints and more. 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.
-87
View File
@@ -1,87 +0,0 @@
site_name: 'wg-easy'
site_description: 'The easiest way to run WireGuard VPN + Web-based Admin UI.'
site_author: 'WireGuard Easy'
copyright: >
<p>
&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'
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
+2 -4
View File
@@ -1,4 +1,2 @@
mkdocs-material zensical==0.0.45
pillow git+https://github.com/squidfunk/mike.git
cairosvg
mike
+97
View File
@@ -0,0 +1,97 @@
[project]
site_name = "wg-easy"
site_description = "The easiest way to run WireGuard VPN + Web-based Admin UI."
site_author = "WireGuard Easy"
copyright = """
<p>
&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"
docs_dir = "content/"
site_url = "https://wg-easy.github.io/wg-easy"
[project.theme]
variant = "modern"
favicon = "assets/logo/favicon.png"
logo = "assets/logo/logo.png"
features = [
"navigation.instant",
"navigation.tabs",
"navigation.expand",
"navigation.path",
"navigation.top",
"content.action.edit",
"content.action.view",
"content.code.copy",
"content.code.annotate",
]
[project.theme.icon]
repo = "fontawesome/brands/github"
# Palette toggle for automatic mode
[[project.theme.palette]]
media = "(prefers-color-scheme)"
toggle.icon = "lucide/sun-moon"
toggle.name = "Switch to light mode"
# Palette toggle for light mode
[[project.theme.palette]]
media = "(prefers-color-scheme: light)"
scheme = "default"
primary = "grey"
accent = "red"
toggle.icon = "lucide/sun"
toggle.name = "Switch to dark mode"
# Palette toggle for dark mode
[[project.theme.palette]]
media = "(prefers-color-scheme: dark)"
scheme = "slate"
primary = "grey"
accent = "red"
toggle.icon = "lucide/moon"
toggle.name = "Switch to system preference"
# [project.plugins.privacy]
[project.extra.version]
provider = "mike"
[project.markdown_extensions.pymdownx.blocks.admonition]
types = ["danger", "note", "info", "question", "warning"]
[project.markdown_extensions.md_in_html]
[project.markdown_extensions.toc]
permalink = true
[project.markdown_extensions.pymdownx.details]
[project.markdown_extensions.pymdownx.emoji]
emoji_generator = "zensical.extensions.emoji.to_svg"
emoji_index = "zensical.extensions.emoji.twemoji"
[project.markdown_extensions.pymdownx.highlight]
anchor_linenums = true
line_spans = "__span"
pygments_lang_class = true
[project.markdown_extensions.pymdownx.inlinehilite]
[project.markdown_extensions.pymdownx.smartsymbols]
[project.markdown_extensions.pymdownx.superfences]
custom_fences = [
{ name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" },
]
+4 -3
View File
@@ -5,12 +5,13 @@
"dev": "docker compose -f docker-compose.dev.yml up wg-easy --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", "cli:dev": "docker compose -f docker-compose.dev.yml run --build --rm -it wg-easy cli:dev",
"build": "docker build -t wg-easy .", "build": "docker build -t wg-easy .",
"docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8080", "docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs zensical/zensical serve -a 0.0.0.0:8080",
"scripts:version": "bash scripts/version.sh", "scripts:version": "bash scripts/version.sh",
"scripts:i18n": "bash scripts/i18n.sh",
"format:check:docs": "prettier --check docs" "format:check:docs": "prettier --check docs"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^3.6.2" "prettier": "^3.9.5"
}, },
"packageManager": "pnpm@10.21.0" "packageManager": "pnpm@11.15.1"
} }
+5 -5
View File
@@ -9,16 +9,16 @@ importers:
.: .:
devDependencies: devDependencies:
prettier: prettier:
specifier: ^3.6.2 specifier: ^3.9.5
version: 3.6.2 version: 3.9.5
packages: packages:
prettier@3.6.2: prettier@3.9.5:
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
snapshots: snapshots:
prettier@3.6.2: {} prettier@3.9.5: {}
+1
View File
@@ -30,6 +30,7 @@ echo "Updated package.json to version $new_version"
echo "----" echo "----"
echo "If you changed the major version, remember to update the docker-compose.yml file and docs (search for: ref: major version)" echo "If you changed the major version, remember to update the docker-compose.yml file and docs (search for: ref: major version)"
echo "Make sure to stage any changes before proceeding (e.g. Changelog updates)."
echo "----" echo "----"
echo "If you did everything press 'y' to commit the changes and create a new tag" echo "If you did everything press 'y' to commit the changes and create a new tag"
+2
View File
@@ -23,4 +23,6 @@ logs
.env.* .env.*
!.env.example !.env.example
coverage/
wg-easy.db wg-easy.db
+1
View File
@@ -0,0 +1 @@
setups.@nuxt/test-utils="4.0.3"
+7
View File
@@ -0,0 +1,7 @@
:root {
color-scheme: light;
}
.dark {
color-scheme: dark;
}
+29
View File
@@ -0,0 +1,29 @@
<template>
<div class="overflow-x-auto rounded border-2 border-red-800 py-2">
<pre
class="mx-2 inline-block"
@click="selectCode"
><code ref="codeBlock">{{ code }}</code></pre>
</div>
</template>
<script setup lang="ts">
defineProps<{
code: string;
}>();
const codeBlock = useTemplateRef('codeBlock');
function selectCode() {
// TODO: keyboard support?
if (codeBlock.value) {
const range = document.createRange();
range.selectNodeContents(codeBlock.value);
const sel = window.getSelection();
if (sel) {
sel.removeAllRanges();
sel.addRange(range);
}
}
}
</script>
+1 -1
View File
@@ -18,7 +18,7 @@
> >
<slot name="description" /> <slot name="description" />
</DialogDescription> </DialogDescription>
<div class="mt-6 flex justify-end gap-2"> <div class="mt-6 flex flex-wrap justify-end gap-2">
<slot name="actions" /> <slot name="actions" />
</div> </div>
</DialogContent> </DialogContent>
@@ -0,0 +1,26 @@
<template>
<component
:is="elementType"
role="button"
class="rounded-lg border-2 border-gray-100 text-gray-500 hover:border-red-800 hover:bg-red-800 hover:text-white focus:border-red-800 focus:outline-0 focus:ring-0 disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-100 disabled:text-gray-400 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400 dark:disabled:border-neutral-800 dark:disabled:bg-neutral-700 dark:disabled:text-neutral-400"
v-bind="attrs"
>
<slot />
</component>
</template>
<script setup lang="ts">
const props = defineProps({
as: {
type: String,
default: 'button',
},
});
const elementType = computed(() => props.as);
const attrs = computed(() => {
const { as, ...rest } = props;
return rest;
});
</script>
+10
View File
@@ -0,0 +1,10 @@
<template>
<textarea
v-model="data"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
/>
</template>
<script lang="ts" setup>
const data = defineModel<string>();
</script>
+1 -1
View File
@@ -16,7 +16,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { ApexOptions } from 'apexcharts'; import type { ApexChart, ApexOptions } from 'apexcharts';
defineProps<{ defineProps<{
client: LocalClient; client: LocalClient;
+2 -2
View File
@@ -1,7 +1,7 @@
<template> <template>
<ClientCardCharts :client="client" /> <ClientCardCharts :client="client" />
<div <div
class="relative z-10 flex flex-col justify-between gap-3 px-3 py-3 sm:flex-row md:py-5" class="relative flex flex-col justify-between gap-3 px-3 py-3 sm:flex-row md:py-5"
> >
<div class="flex w-full items-center gap-3 md:gap-4"> <div class="flex w-full items-center gap-3 md:gap-4">
<ClientCardAvatar :client="client" /> <ClientCardAvatar :client="client" />
@@ -9,7 +9,7 @@
<div class="flex flex-grow flex-col gap-1"> <div class="flex flex-grow flex-col gap-1">
<ClientCardName :client="client" /> <ClientCardName :client="client" />
<div <div
class="flex flex-col pb-1 text-xs text-gray-500 md:inline-block md:pb-0 dark:text-neutral-400" class="flex flex-col text-xs text-gray-500 dark:text-neutral-400"
> >
<div> <div>
<ClientCardAddress :client="client" /> <ClientCardAddress :client="client" />
+1 -3
View File
@@ -1,7 +1,5 @@
<template> <template>
<div <div class="block text-xs text-gray-500 dark:text-neutral-400">
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> <span class="inline-block">{{ expiredDateFormat(client.expiresAt) }}</span>
</div> </div>
</template> </template>
+1 -3
View File
@@ -1,11 +1,9 @@
<template> <template>
<div <div
class="text-sm text-gray-700 md:text-base dark:text-neutral-200" class="break-all text-sm text-gray-700 md:text-base dark:text-neutral-200"
:title="$t('client.createdOn') + $d(new Date(client.createdAt))" :title="$t('client.createdOn') + $d(new Date(client.createdAt))"
> >
<span class="border-b-2 border-t-2 border-transparent">
{{ client.name }} {{ client.name }}
</span>
</div> </div>
</template> </template>
@@ -14,10 +14,11 @@ const props = defineProps<{ client: LocalClient }>();
const clientsStore = useClientsStore(); const clientsStore = useClientsStore();
const _showOneTimeLink = useSubmit( const _showOneTimeLink = useSubmit(
`/api/client/${props.client.id}/generateOneTimeLink`, (data) =>
{ $fetch(`/api/client/${props.client.id}/generateOneTimeLink`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
await clientsStore.refresh(); await clientsStore.refresh();
+8 -6
View File
@@ -18,10 +18,11 @@ const enabled = ref(props.client.enabled);
const clientsStore = useClientsStore(); const clientsStore = useClientsStore();
const _disableClient = useSubmit( const _disableClient = useSubmit(
`/api/client/${props.client.id}/disable`, (data) =>
{ $fetch(`/api/client/${props.client.id}/disable`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
await clientsStore.refresh(); await clientsStore.refresh();
@@ -31,10 +32,11 @@ const _disableClient = useSubmit(
); );
const _enableClient = useSubmit( const _enableClient = useSubmit(
`/api/client/${props.client.id}/enable`, (data) =>
{ $fetch(`/api/client/${props.client.id}/enable`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
await clientsStore.refresh(); await clientsStore.refresh();
@@ -0,0 +1,74 @@
<template>
<BaseDialog :trigger-class="triggerClass">
<template #trigger>
<slot />
</template>
<template #title>
{{ $t('client.config') }}
</template>
<template #description>
<div v-if="status === 'success'">
<BaseCodeBlock :code="config ?? ''" />
</div>
<div v-else>
<span>{{ $t('general.loading') }}</span>
</div>
</template>
<template #actions>
<DialogClose as-child>
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
</DialogClose>
<DialogClose as-child>
<BasePrimaryButton @click="copyCode">
{{ $t('copy.copy') }}
</BasePrimaryButton>
</DialogClose>
</template>
</BaseDialog>
</template>
<script setup lang="ts">
const props = defineProps<{ triggerClass?: string; clientId: number }>();
const toast = useToast();
const { copied, copy, isSupported } = useClipboard({
// fallback does not work
legacy: false,
});
const { data: config, status } = useFetch(
`/api/client/${props.clientId}/configuration`,
{
responseType: 'text',
server: false,
}
);
async function copyCode() {
if (status.value !== 'success') {
return;
}
if (!isSupported.value) {
toast.showToast({
type: 'error',
message: $t('copy.notSupported'),
});
return;
}
await copy(config.value ?? '');
if (copied.value) {
toast.showToast({
type: 'success',
message: $t('copy.copied'),
});
} else {
toast.showToast({
type: 'error',
message: $t('copy.failed'),
});
}
}
</script>
+4 -3
View File
@@ -43,10 +43,11 @@ function createClient() {
} }
const _createClient = useSubmit( const _createClient = useSubmit(
'/api/client', (data) =>
{ $fetch('/api/client', {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: () => clientsStore.refresh(), revert: () => clientsStore.refresh(),
successMsg: t('client.created'), successMsg: t('client.created'),
+1 -1
View File
@@ -3,7 +3,7 @@
{{ $t('client.empty') }}<br /><br /> {{ $t('client.empty') }}<br /><br />
<ClientsCreateDialog> <ClientsCreateDialog>
<BaseSecondaryButton as="span"> <BaseSecondaryButton as="span">
<IconsPlus class="w-4 md:mr-2" /> <IconsPlus class="mr-2 w-4" />
<span class="text-sm">{{ $t('client.new') }}</span> <span class="text-sm">{{ $t('client.new') }}</span>
</BaseSecondaryButton> </BaseSecondaryButton>
</ClientsCreateDialog> </ClientsCreateDialog>
+2 -2
View File
@@ -1,8 +1,8 @@
<template> <template>
<ClientsCreateDialog> <ClientsCreateDialog>
<BaseSecondaryButton as="span"> <BaseSecondaryButton as="span">
<IconsPlus class="w-4 md:mr-2" /> <IconsPlus class="mr-2 w-4" />
<span class="text-sm max-md:hidden">{{ $t('client.newShort') }}</span> <span class="text-sm">{{ $t('client.newShort') }}</span>
</BaseSecondaryButton> </BaseSecondaryButton>
</ClientsCreateDialog> </ClientsCreateDialog>
</template> </template>
+99 -2
View File
@@ -5,11 +5,25 @@
</template> </template>
<template #description> <template #description>
<div class="bg-white"> <div class="bg-white">
<img :src="qrCode" /> <img ref="img" :src="qrCode" />
</div> </div>
</template> </template>
<template #actions> <template #actions>
<DialogClose> <BaseSecondaryButton
class="flex items-center gap-2"
:title="$t('client.copyPng')"
@click="copyPng"
>
<IconsCopy class="size-5" /> PNG
</BaseSecondaryButton>
<BaseSecondaryButton
class="flex items-center gap-2"
:title="$t('client.downloadPng')"
@click="downloadPng"
>
<IconsDownload class="size-5" /> PNG
</BaseSecondaryButton>
<DialogClose as-child>
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton> <BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
</DialogClose> </DialogClose>
</template> </template>
@@ -18,4 +32,87 @@
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ qrCode: string }>(); defineProps<{ qrCode: string }>();
const toast = useToast();
const img = useTemplateRef('img');
async function svgToPng() {
if (!img.value || !img.value.complete || img.value.naturalWidth === 0) {
throw new Error('image is not loaded');
}
const width = 1000;
const height = 1000;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('was not able to create 2d context');
}
ctx.drawImage(img.value!, 0, 0, width, height);
return new Promise<Blob>((res, rej) => {
canvas.toBlob((blob) => {
if (!blob) {
return rej(new Error('was not able to create blob'));
}
return res(blob);
}, 'image/png');
});
}
async function downloadPng() {
try {
const blob = await svgToPng();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'client-config.png';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (e) {
console.error('failed to download png', e);
toast.showToast({
type: 'error',
message: $t('toast.unknown'),
});
}
}
async function copyPng() {
const blob = await svgToPng().catch((e) => {
console.error('failed to convert svg to png', e);
toast.showToast({
type: 'error',
message: $t('toast.unknown'),
});
});
if (!blob) {
return;
}
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
toast.showToast({
type: 'success',
message: $t('copy.copied'),
});
} catch (e) {
console.error('failed to copy png', e);
toast.showToast({
type: 'error',
message: $t('copy.failed'),
});
}
}
</script> </script>
+3 -3
View File
@@ -1,14 +1,14 @@
<template> <template>
<div class="relative w-60 md:mr-2"> <div class="relative">
<div class="relative flex h-full items-center"> <div class="relative flex h-full items-center">
<MagnifyingGlassIcon <IconsMagnifyingGlass
class="absolute left-2.5 h-4 w-4 text-gray-400 dark:text-neutral-500" class="absolute left-2.5 h-4 w-4 text-gray-400 dark:text-neutral-500"
/> />
<input <input
v-model="searchQuery" v-model="searchQuery"
type="text" type="text"
:placeholder="$t('client.search')" :placeholder="$t('client.search')"
class="w-full rounded bg-white py-2 pr-8 text-sm text-gray-900 shadow-sm ring-1 ring-gray-300 transition-all placeholder:text-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-red-600 dark:bg-neutral-800 dark:text-white dark:ring-neutral-700 dark:placeholder:text-neutral-500 dark:focus:ring-red-700" class="w-full rounded bg-white px-8 py-2 text-sm text-gray-900 shadow-sm ring-1 ring-gray-300 transition-all placeholder:text-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-red-600 dark:bg-neutral-800 dark:text-white dark:ring-neutral-700 dark:placeholder:text-neutral-500 dark:focus:ring-red-700"
@input="updateSearch" @input="updateSearch"
/> />
<button <button
+4 -7
View File
@@ -1,11 +1,8 @@
<template> <template>
<BasePrimaryButton @click="toggleSort"> <BasePrimaryButton @click="toggleSort">
<IconsArrowDown <IconsArrowDown v-if="globalStore.sortClient === 'asc'" class="mr-2 w-4" />
v-if="globalStore.sortClient === true" <IconsArrowUp v-else class="mr-2 w-4" />
class="w-4 md:mr-2" <span class="text-sm">{{ $t('client.sort') }}</span>
/>
<IconsArrowUp v-else class="w-4 md:mr-2" />
<span class="text-sm max-md:hidden"> {{ $t('client.sort') }}</span>
</BasePrimaryButton> </BasePrimaryButton>
</template> </template>
@@ -14,7 +11,7 @@ const globalStore = useGlobalStore();
const clientsStore = useClientsStore(); const clientsStore = useClientsStore();
function toggleSort() { function toggleSort() {
globalStore.sortClient = !globalStore.sortClient; globalStore.sortClient = globalStore.sortClient === 'asc' ? 'desc' : 'asc';
clientsStore.refresh().catch(console.error); clientsStore.refresh().catch(console.error);
} }
</script> </script>
+7 -26
View File
@@ -1,12 +1,5 @@
<template> <template>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div
v-if="overridden"
class="flex w-fit items-center gap-2 rounded-lg bg-amber-50 p-2 text-sm text-amber-700 dark:bg-amber-900/20 dark:text-amber-400"
>
<IconsWarning class="size-4" />
<span>This field is overridden by an environment variable</span>
</div>
<div v-if="data?.length === 0"> <div v-if="data?.length === 0">
{{ emptyText || $t('form.noItems') }} {{ emptyText || $t('form.noItems') }}
</div> </div>
@@ -19,34 +12,22 @@
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
@input="update($event, i)" @input="update($event, i)"
/> />
<BaseSecondaryButton <BaseSecondaryButton type="button" class="rounded-lg" @click="del(i)">
as="input" {{ '-' }}
type="button" </BaseSecondaryButton>
class="rounded-lg"
value="-"
@click="del(i)"
/>
</div> </div>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<BasePrimaryButton <BasePrimaryButton type="button" class="rounded-lg" @click="add">
as="input" {{ $t('form.add') }}
type="button" </BasePrimaryButton>
class="rounded-lg"
:value="$t('form.add')"
@click="add"
/>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const data = defineModel<string[]>(); const data = defineModel<string[]>();
defineProps<{ defineProps<{ emptyText?: string[]; name: string }>();
emptyText?: string[];
name: string;
overridden?: boolean;
}>();
function update(e: Event, i: number) { function update(e: Event, i: number) {
const v = (e.target as HTMLInputElement).value; const v = (e.target as HTMLInputElement).value;
+2 -2
View File
@@ -1,10 +1,10 @@
<template> <template>
<h4 class="col-span-full flex items-center py-6 text-2xl"> <h3 class="col-span-full flex items-center py-6 text-2xl">
<slot /> <slot />
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>
</h4> </h3>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
-7
View File
@@ -6,12 +6,6 @@
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div> </div>
<div class="flex gap-1"> <div class="flex gap-1">
<BaseInput <BaseInput
@@ -44,7 +38,6 @@ defineProps<{
description?: string; description?: string;
placeholder?: string; placeholder?: string;
url: '/api/admin/ip-info' | '/api/setup/4'; url: '/api/admin/ip-info' | '/api/setup/4';
overridden?: boolean;
}>(); }>();
const data = defineModel<string | null>({ const data = defineModel<string | null>({
+3 -3
View File
@@ -1,7 +1,7 @@
<template> <template>
<RLabel :for="props.for" class="md:align-middle md:leading-10" <RLabel :for="props.for" class="md:leading-[2.75rem]">
><slot <slot />
/></RLabel> </RLabel>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
+6 -14
View File
@@ -12,23 +12,15 @@
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
@input="update($event, i)" @input="update($event, i)"
/> />
<BaseSecondaryButton <BaseSecondaryButton type="button" class="rounded-lg" @click="del(i)">
as="input" {{ '-' }}
type="button" </BaseSecondaryButton>
class="rounded-lg"
value="-"
@click="del(i)"
/>
</div> </div>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<BasePrimaryButton <BasePrimaryButton type="button" class="rounded-lg" @click="add">
as="input" {{ $t('form.add') }}
type="button" </BasePrimaryButton>
class="rounded-lg"
:value="$t('form.add')"
@click="add"
/>
</div> </div>
</div> </div>
</template> </template>
@@ -6,12 +6,6 @@
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div> </div>
<BaseInput <BaseInput
:id="id" :id="id"
@@ -30,7 +24,6 @@ defineProps<{
description?: string; description?: string;
autocomplete?: string; autocomplete?: string;
placeholder?: string; placeholder?: string;
overridden?: boolean;
}>(); }>();
const data = defineModel<string | null>({ const data = defineModel<string | null>({
+1 -12
View File
@@ -6,23 +6,12 @@
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div> </div>
<BaseInput :id="id" v-model.number="data" :name="id" type="number" /> <BaseInput :id="id" v-model.number="data" :name="id" type="number" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
defineProps<{ defineProps<{ id: string; label: string; description?: string }>();
id: string;
label: string;
description?: string;
overridden?: boolean;
}>();
const data = defineModel<number>(); const data = defineModel<number>();
</script> </script>
@@ -2,7 +2,7 @@
<input <input
:value="label" :value="label"
:type="type ?? 'button'" :type="type ?? 'button'"
class="col-span-2 rounded-lg border-2 border-red-800 bg-red-800 py-2 text-white hover:border-red-600 hover:bg-red-600 focus:border-red-800 focus:outline-0 focus:ring-0" class="col-span-2 rounded-lg border-2 border-red-800 bg-red-800 px-4 py-2 text-white hover:border-red-600 hover:bg-red-600 focus:border-red-800 focus:outline-0 focus:ring-0"
/> />
</template> </template>
@@ -1,16 +1,32 @@
<template> <template>
<input <BaseFormSecondaryButton
:value="label" :as="as"
:type="type ?? 'button'" :value="isInput ? label : undefined"
class="col-span-2 rounded-lg border-2 border-gray-100 py-2 text-gray-500 hover:border-red-800 hover:bg-red-800 hover:text-white focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" :type="isInput ? type : undefined"
/> class="col-span-2 px-4 py-2"
>
<template v-if="!isInput">
{{ label }}
</template>
</BaseFormSecondaryButton>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { InputTypeHTMLAttribute } from 'vue'; import type { InputTypeHTMLAttribute } from 'vue';
const props = withDefaults(
defineProps<{ defineProps<{
label: string; label: string;
type?: InputTypeHTMLAttribute; type?: InputTypeHTMLAttribute;
}>(); as?: string;
}>(),
{
type: 'button',
as: 'input',
}
);
const isInput = computed(() => {
return props.as === 'input';
});
</script> </script>
+3 -12
View File
@@ -6,22 +6,13 @@
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div> </div>
<div class="my-auto">
<BaseSwitch :id="id" v-model="data" /> <BaseSwitch :id="id" v-model="data" />
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
defineProps<{ defineProps<{ id: string; label: string; description?: string }>();
id: string;
label: string;
description?: string;
overridden?: boolean;
}>();
const data = defineModel<boolean>(); const data = defineModel<boolean>();
</script> </script>
+29
View File
@@ -0,0 +1,29 @@
<template>
<div class="flex items-center">
<FormLabel :for="id">
{{ label }}
</FormLabel>
<BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" />
</BaseTooltip>
</div>
<BaseTextArea
:id="id"
v-model.trim="data"
:name="id"
:autocomplete="autocomplete"
:disabled="disabled"
/>
</template>
<script lang="ts" setup>
defineProps<{
id: string;
label: string;
description?: string;
autocomplete?: string;
disabled?: boolean;
}>();
const data = defineModel<string>();
</script>
-7
View File
@@ -6,12 +6,6 @@
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div> </div>
<BaseInput <BaseInput
:id="id" :id="id"
@@ -30,7 +24,6 @@ defineProps<{
description?: string; description?: string;
autocomplete?: string; autocomplete?: string;
disabled?: boolean; disabled?: boolean;
overridden?: boolean;
}>(); }>();
const data = defineModel<string>(); const data = defineModel<string>();
+2 -2
View File
@@ -1,12 +1,12 @@
<template> <template>
<Toggle <Toggle
:pressed="globalStore.uiShowCharts" :pressed="globalStore.uiShowCharts"
class="group inline-flex h-8 w-8 cursor-pointer items-center justify-center whitespace-nowrap rounded-full bg-gray-200 transition hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600" class="group flex h-8 w-8 items-center justify-center rounded-full bg-gray-200 transition hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600"
:title="$t('layout.toggleCharts')" :title="$t('layout.toggleCharts')"
@update:pressed="globalStore.toggleCharts" @update:pressed="globalStore.toggleCharts"
> >
<IconsChart <IconsChart
class="h-5 w-5 fill-gray-400 transition group-data-[state=on]:fill-gray-600 dark:fill-neutral-600 dark:group-data-[state=on]:fill-neutral-400" class="h-5 w-5 transition group-data-[state=on]:fill-gray-600 dark:text-neutral-400 dark:group-data-[state=on]:fill-gray-300"
/> />
</Toggle> </Toggle>
</template> </template>
+4 -2
View File
@@ -1,12 +1,14 @@
<template> <template>
<SelectRoot v-model="langProxy" :default-value="locale"> <SelectRoot v-model="langProxy" :default-value="locale">
<SelectTrigger <SelectTrigger
class="inline-flex h-8 items-center justify-around gap-2 rounded bg-gray-200 px-3 text-sm leading-none dark:bg-neutral-700 dark:text-neutral-400" class="group inline-flex h-8 items-center justify-around gap-2 rounded bg-gray-200 px-3 text-sm leading-none dark:bg-neutral-700 dark:text-neutral-400"
aria-label="Select language" aria-label="Select language"
> >
<IconsLanguage class="size-3" /> <IconsLanguage class="size-3" />
<SelectValue /> <SelectValue />
<IconsArrowDown class="size-3" /> <IconsArrowDown
class="size-3 transition-transform group-data-[state=open]:rotate-180"
/>
</SelectTrigger> </SelectTrigger>
<SelectPortal> <SelectPortal>
+1 -1
View File
@@ -1,5 +1,5 @@
<template> <template>
<NuxtLink to="/" class="mb-4"> <NuxtLink to="/" class="max-sm:mb-4">
<h1 class="text-4xl font-medium dark:text-neutral-200"> <h1 class="text-4xl font-medium dark:text-neutral-200">
<img <img
src="/logo.png" src="/logo.png"
+1 -1
View File
@@ -5,7 +5,7 @@
authStore.userData && authStore.userData &&
hasPermissions(authStore.userData, 'admin', 'any') hasPermissions(authStore.userData, 'admin', 'any')
" "
class="font-small mb-10 rounded-md bg-red-800 p-4 text-sm text-white shadow-lg dark:bg-red-100 dark:text-red-600" class="font-small rounded-md bg-red-800 p-4 text-sm text-white shadow-lg dark:bg-red-100 dark:text-red-600"
:title="`v${globalStore.information.currentRelease} → v${globalStore.information.latestRelease.version}`" :title="`v${globalStore.information.currentRelease} → v${globalStore.information.latestRelease.version}`"
> >
<div class="container mx-auto flex flex-auto flex-row items-center"> <div class="container mx-auto flex flex-auto flex-row items-center">
@@ -0,0 +1,23 @@
<template>
<!-- https://brand.github.com/foundations/logo -->
<svg
width="98"
height="96"
viewBox="0 0 98 96"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_730_27136)">
<path
d="M41.4395 69.3848C28.8066 67.8535 19.9062 58.7617 19.9062 46.9902C19.9062 42.2051 21.6289 37.0371 24.5 33.5918C23.2559 30.4336 23.4473 23.7344 24.8828 20.959C28.7109 20.4805 33.8789 22.4902 36.9414 25.2656C40.5781 24.1172 44.4062 23.543 49.0957 23.543C53.7852 23.543 57.6133 24.1172 61.0586 25.1699C64.0254 22.4902 69.2891 20.4805 73.1172 20.959C74.457 23.543 74.6484 30.2422 73.4043 33.4961C76.4668 37.1328 78.0937 42.0137 78.0937 46.9902C78.0937 58.7617 69.1934 67.6621 56.3691 69.2891C59.623 71.3945 61.8242 75.9883 61.8242 81.252L61.8242 91.2051C61.8242 94.0762 64.2168 95.7031 67.0879 94.5547C84.4102 87.9512 98 70.6289 98 49.1914C98 22.1074 75.9883 6.69539e-07 48.9043 4.309e-07C21.8203 1.92261e-07 -1.9479e-07 22.1074 -4.3343e-07 49.1914C-6.20631e-07 70.4375 13.4941 88.0469 31.6777 94.6504C34.2617 95.6074 36.75 93.8848 36.75 91.3008L36.75 83.6445C35.4102 84.2188 33.6875 84.6016 32.1562 84.6016C25.8398 84.6016 22.1074 81.1563 19.4277 74.7441C18.375 72.1602 17.2266 70.6289 15.0254 70.3418C13.877 70.2461 13.4941 69.7676 13.4941 69.1934C13.4941 68.0449 15.4082 67.1836 17.3223 67.1836C20.0977 67.1836 22.4902 68.9063 24.9785 72.4473C26.8926 75.2227 28.9023 76.4668 31.2949 76.4668C33.6875 76.4668 35.2187 75.6055 37.4199 73.4043C39.0469 71.7773 40.291 70.3418 41.4395 69.3848Z"
fill="white"
class="fill-black dark:fill-white"
/>
</g>
<defs>
<clipPath id="clip0_730_27136">
<rect width="98" height="96" fill="white" />
</clipPath>
</defs>
</svg>
</template>
+247
View File
@@ -0,0 +1,247 @@
<template>
<!-- By Original: Google Vector: Designism, Liquinoid, and YeBoy371 - Own work based on: Google updating its G icon for the first time in 10 years, Public Domain, https://commons.wikimedia.org/w/index.php?curid=165142698 -->
<svg
version="1.1"
viewBox="0 0 268.1522 273.8827"
overflow="hidden"
xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
>
<defs>
<linearGradient id="a">
<stop offset="0" stop-color="#0fbc5c" />
<stop offset="1" stop-color="#0cba65" />
</linearGradient>
<linearGradient id="g">
<stop offset=".2312727" stop-color="#0fbc5f" />
<stop offset=".3115468" stop-color="#0fbc5f" />
<stop offset=".3660131" stop-color="#0fbc5e" />
<stop offset=".4575163" stop-color="#0fbc5d" />
<stop offset=".540305" stop-color="#12bc58" />
<stop offset=".6993464" stop-color="#28bf3c" />
<stop offset=".7712418" stop-color="#38c02b" />
<stop offset=".8605665" stop-color="#52c218" />
<stop offset=".9150327" stop-color="#67c30f" />
<stop offset="1" stop-color="#86c504" />
</linearGradient>
<linearGradient id="h">
<stop offset=".1416122" stop-color="#1abd4d" />
<stop offset=".2475151" stop-color="#6ec30d" />
<stop offset=".3115468" stop-color="#8ac502" />
<stop offset=".3660131" stop-color="#a2c600" />
<stop offset=".4456735" stop-color="#c8c903" />
<stop offset=".540305" stop-color="#ebcb03" />
<stop offset=".6156363" stop-color="#f7cd07" />
<stop offset=".6993454" stop-color="#fdcd04" />
<stop offset=".7712418" stop-color="#fdce05" />
<stop offset=".8605661" stop-color="#ffce0a" />
</linearGradient>
<linearGradient id="f">
<stop offset=".3159041" stop-color="#ff4c3c" />
<stop offset=".6038179" stop-color="#ff692c" />
<stop offset=".7268366" stop-color="#ff7825" />
<stop offset=".884534" stop-color="#ff8d1b" />
<stop offset="1" stop-color="#ff9f13" />
</linearGradient>
<linearGradient id="b">
<stop offset=".2312727" stop-color="#ff4541" />
<stop offset=".3115468" stop-color="#ff4540" />
<stop offset=".4575163" stop-color="#ff4640" />
<stop offset=".540305" stop-color="#ff473f" />
<stop offset=".6993464" stop-color="#ff5138" />
<stop offset=".7712418" stop-color="#ff5b33" />
<stop offset=".8605665" stop-color="#ff6c29" />
<stop offset="1" stop-color="#ff8c18" />
</linearGradient>
<linearGradient id="d">
<stop offset=".4084578" stop-color="#fb4e5a" />
<stop offset="1" stop-color="#ff4540" />
</linearGradient>
<linearGradient id="c">
<stop offset=".1315461" stop-color="#0cba65" />
<stop offset=".2097843" stop-color="#0bb86d" />
<stop offset=".2972969" stop-color="#09b479" />
<stop offset=".3962575" stop-color="#08ad93" />
<stop offset=".4771242" stop-color="#0aa6a9" />
<stop offset=".5684245" stop-color="#0d9cc6" />
<stop offset=".667385" stop-color="#1893dd" />
<stop offset=".7687273" stop-color="#258bf1" />
<stop offset=".8585063" stop-color="#3086ff" />
</linearGradient>
<linearGradient id="e">
<stop offset=".3660131" stop-color="#ff4e3a" />
<stop offset=".4575163" stop-color="#ff8a1b" />
<stop offset=".540305" stop-color="#ffa312" />
<stop offset=".6156363" stop-color="#ffb60c" />
<stop offset=".7712418" stop-color="#ffcd0a" />
<stop offset=".8605665" stop-color="#fecf0a" />
<stop offset=".9150327" stop-color="#fecf08" />
<stop offset="1" stop-color="#fdcd01" />
</linearGradient>
<linearGradient
id="s"
xlink:href="#a"
x1="219.6997"
y1="329.5351"
x2="254.4673"
y2="329.5351"
gradientUnits="userSpaceOnUse"
/>
<radialGradient
id="m"
xlink:href="#b"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-1.936885,1.043001,1.455731,2.555422,290.5254,-400.6338)"
cx="109.6267"
cy="135.8619"
fx="109.6267"
fy="135.8619"
r="71.46001"
/>
<radialGradient
id="n"
xlink:href="#c"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-3.512595,-4.45809,-1.692547,1.260616,870.8006,191.554)"
cx="45.25866"
cy="279.2738"
fx="45.25866"
fy="279.2738"
r="71.46001"
/>
<radialGradient
id="l"
xlink:href="#d"
cx="304.0166"
cy="118.0089"
fx="304.0166"
fy="118.0089"
r="47.85445"
gradientTransform="matrix(2.064353,-4.926832e-6,-2.901531e-6,2.592041,-297.6788,-151.7469)"
gradientUnits="userSpaceOnUse"
/>
<radialGradient
id="o"
xlink:href="#e"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.2485783,2.083138,2.962486,0.3341668,-255.1463,-331.1636)"
cx="181.001"
cy="177.2013"
fx="181.001"
fy="177.2013"
r="71.46001"
/>
<radialGradient
id="p"
xlink:href="#f"
cx="207.6733"
cy="108.0972"
fx="207.6733"
fy="108.0972"
r="41.1025"
gradientTransform="matrix(-1.249206,1.343263,-3.896837,-3.425693,880.5011,194.9051)"
gradientUnits="userSpaceOnUse"
/>
<radialGradient
id="r"
xlink:href="#g"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-1.936885,-1.043001,1.455731,-2.555422,290.5254,838.6834)"
cx="109.6267"
cy="135.8619"
fx="109.6267"
fy="135.8619"
r="71.46001"
/>
<radialGradient
id="j"
xlink:href="#h"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.081402,-1.93722,2.926737,-0.1162508,-215.1345,632.8606)"
cx="154.8697"
cy="145.9691"
fx="154.8697"
fy="145.9691"
r="71.46001"
/>
<filter
id="q"
x="-.04842873"
y="-.0582241"
width="1.096857"
height="1.116448"
color-interpolation-filters="sRGB"
>
<feGaussianBlur stdDeviation="1.700914" />
</filter>
<filter
id="k"
x="-.01670084"
y="-.01009856"
width="1.033402"
height="1.020197"
color-interpolation-filters="sRGB"
>
<feGaussianBlur stdDeviation=".2419367" />
</filter>
<clipPath id="i" clipPathUnits="userSpaceOnUse">
<path
d="M371.3784 193.2406H237.0825v53.4375h77.167c-1.2405 7.5627-4.0259 15.0024-8.1049 21.7862-4.6734 7.7723-10.4511 13.6895-16.373 18.1957-17.7389 13.4983-38.42 16.2584-52.7828 16.2584-36.2824 0-67.2833-23.2865-79.2844-54.9287-.4843-1.1482-.8059-2.3344-1.1975-3.5068-2.652-8.0533-4.101-16.5825-4.101-25.4474 0-9.226 1.5691-18.0575 4.4301-26.3985 11.2851-32.8967 42.9849-57.4674 80.1789-57.4674 7.4811 0 14.6854.8843 21.5173 2.6481 15.6135 4.0309 26.6578 11.9698 33.4252 18.2494l40.834-39.7111c-24.839-22.616-57.2194-36.3201-95.8444-36.3201-30.8782-.00066-59.3863 9.55308-82.7477 25.6992-18.9454 13.0941-34.4833 30.6254-44.9695 50.9861-9.75366 18.8785-15.09441 39.7994-15.09441 62.2934 0 22.495 5.34891 43.6334 15.10261 62.3374v.126c10.3023 19.8567 25.3678 36.9537 43.6783 49.9878 15.9962 11.3866 44.6789 26.5516 84.0307 26.5516 22.6301 0 42.6867-4.0517 60.3748-11.6447 12.76-5.4775 24.0655-12.6217 34.3012-21.8036 13.5247-12.1323 24.1168-27.1388 31.3465-44.4041 7.2297-17.2654 11.097-36.7895 11.097-57.957 0-9.858-.9971-19.8694-2.6881-28.9684Z"
fill="#000"
/>
</clipPath>
</defs>
<g transform="matrix(0.957922,0,0,0.985255,-90.17436,-78.85577)">
<g clip-path="url(#i)">
<path
d="M92.07563 219.9585c.14844 22.14 6.5014 44.983 16.11767 63.4234v.1269c6.9482 13.3919 16.4444 23.9704 27.2604 34.4518l65.326-23.67c-12.3593-6.2344-14.2452-10.0546-23.1048-17.0253-9.0537-9.0658-15.8015-19.4735-20.0038-31.677h-.1693l.1693-.1269c-2.7646-8.0587-3.0373-16.6129-3.1393-25.5029Z"
fill="url(#j)"
filter="url(#k)"
/>
<path
d="M237.0835 79.02491c-6.4568 22.52569-3.988 44.42139 0 57.16129 7.4561.0055 14.6388.8881 21.4494 2.6464 15.6135 4.0309 26.6566 11.97 33.424 18.2496l41.8794-40.7256c-24.8094-22.58904-54.6663-37.2961-96.7528-37.33169Z"
fill="url(#l)"
filter="url(#k)"
/>
<path
d="M236.9434 78.84678c-31.6709-.00068-60.9107 9.79833-84.8718 26.35902-8.8968 6.149-17.0612 13.2521-24.3311 21.1509-1.9045 17.7429 14.2569 39.5507 46.2615 39.3702 15.5284-17.9373 38.4946-29.5427 64.0561-29.5427.0233 0 .046.0019.0693.002l-1.0439-57.33536c-.0472-.00003-.0929-.00406-.1401-.00406Z"
fill="url(#m)"
filter="url(#k)"
/>
<path
d="m341.4751 226.3788-28.2685 19.2848c-1.2405 7.5627-4.0278 15.0023-8.1068 21.7861-4.6734 7.7723-10.4506 13.6898-16.3725 18.196-17.7022 13.4704-38.3286 16.2439-52.6877 16.2553-14.8415 25.1018-17.4435 37.6749 1.0439 57.9342 22.8762-.0167 43.157-4.1174 61.0458-11.7965 12.9312-5.551 24.3879-12.7913 34.7609-22.0964 13.7061-12.295 24.4421-27.5034 31.7688-45.0003 7.3267-17.497 11.2446-37.2822 11.2446-58.7336Z"
fill="url(#n)"
filter="url(#k)"
/>
<path
d="M234.9956 191.2104v57.4981h136.0062c1.1962-7.8745 5.1523-18.0644 5.1523-26.5001 0-9.858-.9963-21.899-2.6873-30.998Z"
fill="#3086ff"
filter="url(#k)"
/>
<path
d="M128.3894 124.3268c-8.393 9.1191-15.5632 19.326-21.2483 30.3646-9.75351 18.8785-15.09402 41.8295-15.09402 64.3235 0 .317.02642.6271.02855.9436 4.31953 8.2244 59.66647 6.6495 62.45617 0-.0035-.3103-.0387-.6128-.0387-.9238 0-9.226 1.5696-16.0262 4.4306-24.3672 3.5294-10.2885 9.0557-19.7628 16.1223-27.9257 1.6019-2.0309 5.8748-6.3969 7.1214-9.0157.4749-.9975-.8621-1.5574-.9369-1.9085-.0836-.3927-1.8762-.0769-2.2778-.3694-1.2751-.9288-3.8001-1.4138-5.3334-1.8449-3.2772-.9215-8.7085-2.9536-11.7252-5.0601-9.5357-6.6586-24.417-14.6122-33.5047-24.2164Z"
fill="url(#o)"
filter="url(#k)"
/>
<path
d="M162.0989 155.8569c22.1123 13.3013 28.4714-6.7139 43.173-12.9771L179.698 90.21568c-9.4075 3.92642-18.2957 8.80465-26.5426 14.50442-12.316 8.5122-23.192 18.8995-32.1763 30.7204Z"
fill="url(#p)"
filter="url(#q)"
/>
<path
d="M171.0987 290.222c-29.6829 10.6413-34.3299 11.023-37.0622 29.2903 5.2213 5.0597 10.8312 9.74 16.7926 13.9835 15.9962 11.3867 46.766 26.5517 86.1178 26.5517.0462 0 .0904-.004.1366-.004v-59.1574c-.0298.0001-.064.002-.0938.002-14.7359 0-26.5113-3.8435-38.5848-10.5273-2.9768-1.6479-8.3775 2.7772-11.1229.799-3.7865-2.7284-12.8991 2.3508-16.1833-.9378Z"
fill="url(#r)"
filter="url(#k)"
/>
<path
d="M219.6997 299.0227v59.9959c5.506.6402 11.2361 1.0289 17.2472 1.0289 6.0259 0 11.8556-.3073 17.5204-.8723v-59.7481c-6.3482 1.0777-12.3272 1.461-17.4776 1.461-5.9318 0-11.7005-.6858-17.29-1.8654Z"
opacity=".5"
fill="url(#s)"
filter="url(#k)"
/>
</g>
</g>
</svg>
</template>
@@ -0,0 +1,10 @@
<template>
<IconsBrandsGitHub v-if="provider === 'github'" />
<IconsBrandsGoogle v-else-if="provider === 'google'" />
</template>
<script setup lang="ts">
defineProps<{
provider: OAUTH_PROVIDER;
}>();
</script>
+7
View File
@@ -0,0 +1,7 @@
<template>
<ClipboardDocumentIcon />
</template>
<script lang="ts" setup>
import ClipboardDocumentIcon from '@heroicons/vue/24/outline/esm/ClipboardDocumentIcon';
</script>
@@ -0,0 +1,7 @@
<template>
<MagnifyingGlassIcon />
</template>
<script lang="ts" setup>
import MagnifyingGlassIcon from '@heroicons/vue/24/outline/esm/MagnifyingGlassIcon';
</script>
+3 -1
View File
@@ -1,7 +1,9 @@
<template> <template>
<div class="container mx-auto max-w-3xl">
<div <div
class="container mx-auto max-w-3xl overflow-hidden rounded-lg bg-white px-3 text-gray-700 shadow-md md:px-0 dark:bg-neutral-700 dark:text-neutral-200" class="mx-3 overflow-hidden rounded-lg bg-white text-gray-700 shadow-md dark:bg-neutral-700 dark:text-neutral-200"
> >
<slot /> <slot />
</div> </div>
</div>
</template> </template>
+1 -1
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="flex flex-shrink-0 items-center space-x-2"> <div class="flex flex-shrink-0 flex-col items-center gap-2 sm:flex-row">
<slot /> <slot />
</div> </div>
</template> </template>
+1 -1
View File
@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="flex flex-auto flex-grow flex-row items-center border-b-2 border-gray-100 p-3 px-5 dark:border-neutral-600" class="flex flex-col items-center gap-2 border-b-2 border-gray-100 p-3 px-5 sm:flex-row dark:border-neutral-600"
> >
<slot /> <slot />
</div> </div>
+2 -8
View File
@@ -1,11 +1,5 @@
<template> <template>
<h2 class="flex-1 text-2xl font-medium"> <h2 class="flex-1 break-all text-2xl font-medium">
{{ text }} <slot />
</h2> </h2>
</template> </template>
<script setup lang="ts">
const { text } = defineProps<{
text: string;
}>();
</script>
+1 -1
View File
@@ -7,7 +7,7 @@
href="https://github.com/wg-easy/wg-easy" href="https://github.com/wg-easy/wg-easy"
>WireGuard Easy</a >WireGuard Easy</a
> >
({{ globalStore.information?.currentRelease }}) © 2021-2025 by ({{ globalStore.information?.currentRelease }}) © 2021-2026 by
<a <a
class="hover:underline" class="hover:underline"
target="_blank" target="_blank"
@@ -0,0 +1,21 @@
<template>
<a
:href="`/api/auth/${provider}`"
class="flex items-center justify-center gap-2 rounded border border-gray-300 bg-white py-2 text-sm text-gray-700 shadow-sm transition hover:bg-gray-50 dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-200 dark:hover:bg-neutral-700"
>
<IconsBrandsProvider :provider="provider" class="h-4 w-4" />
<span>
{{ $t('login.signInWith', [info.friendlyName]) }}
</span>
</a>
</template>
<script setup lang="ts">
defineProps<{
info: {
enabled: boolean;
friendlyName: string;
};
provider: OAUTH_PROVIDER;
}>();
</script>
+4 -3
View File
@@ -70,10 +70,11 @@ const authStore = useAuthStore();
const toggleState = ref(false); const toggleState = ref(false);
const _submit = useSubmit( const _submit = useSubmit(
'/api/session', (data) =>
{ $fetch('/api/session', {
method: 'delete', method: 'delete',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
await navigateTo('/login'); await navigateTo('/login');
+11 -36
View File
@@ -1,49 +1,24 @@
import type {
NitroFetchRequest,
NitroFetchOptions,
TypedInternalResponse,
ExtractedRouteMethod,
} from 'nitropack/types';
import { FetchError } from 'ofetch'; import { FetchError } from 'ofetch';
type RevertFn< type RevertFn<T> = (success: boolean, data: T | undefined) => Promise<void>;
R extends NitroFetchRequest,
T = unknown,
O extends NitroFetchOptions<R> = NitroFetchOptions<R>,
> = (
success: boolean,
data:
| TypedInternalResponse<
R,
T,
NitroFetchOptions<R> extends O ? 'get' : ExtractedRouteMethod<R, O>
>
| undefined
) => Promise<void>;
type SubmitOpts< type SubmitOpts<T> = {
R extends NitroFetchRequest, revert: RevertFn<T>;
T = unknown,
O extends NitroFetchOptions<R> = NitroFetchOptions<R>,
> = {
revert: RevertFn<R, T, O>;
successMsg?: string; successMsg?: string;
noSuccessToast?: boolean; noSuccessToast?: boolean;
}; };
export function useSubmit< type Body = Record<string, unknown> | null | undefined;
R extends NitroFetchRequest,
O extends NitroFetchOptions<R> & { body?: never }, export function useSubmit<T>(
T = unknown, fetcher: (data: Body) => Promise<T>,
>(url: R, options: O, opts: SubmitOpts<R, T, O>) { opts: SubmitOpts<T>
) {
const toast = useToast(); const toast = useToast();
return async (data: unknown) => { return async (data: Body) => {
try { try {
const res = await $fetch(url, { const res = await fetcher(data);
...options,
body: data,
});
if (!opts.noSuccessToast) { if (!opts.noSuccessToast) {
toast.showToast({ toast.showToast({
+7 -3
View File
@@ -1,6 +1,8 @@
<template> <template>
<div> <div>
<header class="mx-auto mt-4 flex max-w-3xl flex-col justify-center"> <header
class="mx-auto my-4 flex max-w-3xl flex-col justify-center max-md:px-3"
>
<div <div
class="mb-5 w-full" class="mb-5 w-full"
:class=" :class="
@@ -17,7 +19,7 @@
<UiUserMenu v-if="loggedIn" /> <UiUserMenu v-if="loggedIn" />
</div> </div>
</div> </div>
<HeaderUpdate class="mt-4" /> <HeaderUpdate class="my-4" />
</header> </header>
<slot /> <slot />
<UiFooter /> <UiFooter />
@@ -27,5 +29,7 @@
<script setup lang="ts"> <script setup lang="ts">
const route = useRoute(); const route = useRoute();
const loggedIn = computed(() => route.path !== '/login'); const loggedIn = computed(
() => route.path !== '/login' && route.path !== '/login/2fa'
);
</script> </script>
+11 -7
View File
@@ -4,25 +4,29 @@ export default defineNuxtRouteMiddleware(async (to) => {
return; return;
} }
const authStore = useAuthStore(); const event = useRequestEvent();
const userData = await authStore.getSession();
// skip login if already logged in const authStore = useAuthStore();
if (to.path === '/login') { authStore.userData = await authStore.getSession(event);
if (userData?.username) {
const isLoginRoute = to.path === '/login' || to.path === '/login/2fa';
// skip login pages if already logged in
if (isLoginRoute) {
if (authStore.userData?.username) {
return navigateTo('/', { redirectCode: 302 }); return navigateTo('/', { redirectCode: 302 });
} }
return; return;
} }
// Require auth for every page other than Login // Require auth for every page other than Login
if (!userData?.username) { if (!authStore.userData?.username) {
return navigateTo('/login', { redirectCode: 302 }); return navigateTo('/login', { redirectCode: 302 });
} }
// Check for admin access // Check for admin access
if (to.path.startsWith('/admin')) { if (to.path.startsWith('/admin')) {
if (!hasPermissions(userData, 'admin', 'any')) { if (!hasPermissions(authStore.userData, 'admin', 'any')) {
return abortNavigation('Not allowed to access Admin Panel'); return abortNavigation('Not allowed to access Admin Panel');
} }
} }
+22 -12
View File
@@ -2,34 +2,47 @@
<div> <div>
<div class="container mx-auto p-4"> <div class="container mx-auto p-4">
<div class="flex flex-col gap-4 lg:flex-row"> <div class="flex flex-col gap-4 lg:flex-row">
<div class="rounded-lg bg-white p-4 lg:w-64 dark:bg-neutral-700"> <div
class="overflow-hidden rounded-lg bg-white text-gray-700 shadow-md lg:w-64 dark:bg-neutral-700 dark:text-neutral-200"
>
<PanelHead>
<PanelHeadTitle>
<NuxtLink to="/admin"> <NuxtLink to="/admin">
<h2 class="mb-4 text-xl font-bold dark:text-neutral-200">
{{ t('pages.admin.panel') }} {{ t('pages.admin.panel') }}
</h2>
</NuxtLink> </NuxtLink>
<div class="flex flex-col space-y-2"> </PanelHeadTitle>
</PanelHead>
<PanelBody>
<nav class="flex flex-col gap-2">
<NuxtLink <NuxtLink
v-for="(item, index) in menuItems" v-for="(item, index) in menuItems"
:key="index" :key="index"
:to="`/admin/${item.id}`" :to="`/admin/${item.id}`"
active-class="bg-red-800 rounded" class="group rounded"
active-class="bg-red-800 active"
> >
<BaseSecondaryButton <BaseSecondaryButton
as="span" as="span"
class="w-full cursor-pointer rounded p-2 font-medium transition-colors duration-200 hover:bg-red-800 dark:text-neutral-200" class="w-full font-medium group-[.active]:text-white"
> >
{{ item.name }} {{ item.name }}
</BaseSecondaryButton> </BaseSecondaryButton>
</NuxtLink> </NuxtLink>
</div> </nav>
</PanelBody>
</div> </div>
<div <div
class="flex-1 rounded-lg bg-white p-6 dark:bg-neutral-700 dark:text-neutral-200" class="flex-1 overflow-hidden rounded-lg bg-white text-gray-700 shadow-md dark:bg-neutral-700 dark:text-neutral-200"
> >
<h1 class="mb-6 text-3xl font-bold">{{ activeMenuItem.name }}</h1> <PanelHead>
<PanelHeadTitle>
{{ activeMenuItem.name }}
</PanelHeadTitle>
</PanelHead>
<PanelBody>
<NuxtPage /> <NuxtPage />
</PanelBody>
</div> </div>
</div> </div>
</div> </div>
@@ -37,9 +50,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const authStore = useAuthStore();
authStore.update();
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
+5 -19
View File
@@ -9,14 +9,12 @@
:label="$t('general.host')" :label="$t('general.host')"
:description="$t('admin.config.hostDesc')" :description="$t('admin.config.hostDesc')"
url="/api/admin/ip-info" url="/api/admin/ip-info"
:overridden="overrides?.host"
/> />
<FormNumberField <FormNumberField
id="port" id="port"
v-model="data.port" v-model="data.port"
:label="$t('general.port')" :label="$t('general.port')"
:description="$t('admin.config.portDesc')" :description="$t('admin.config.portDesc')"
:overridden="overrides?.port"
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
@@ -26,18 +24,13 @@
<FormArrayField <FormArrayField
v-model="data.defaultAllowedIps" v-model="data.defaultAllowedIps"
name="defaultAllowedIps" name="defaultAllowedIps"
:overridden="overrides?.defaultAllowedIps"
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormHeading :description="$t('admin.config.dnsDesc')"> <FormHeading :description="$t('admin.config.dnsDesc')">
{{ $t('general.dns') }} {{ $t('general.dns') }}
</FormHeading> </FormHeading>
<FormArrayField <FormArrayField v-model="data.defaultDns" name="defaultDns" />
v-model="data.defaultDns"
name="defaultDns"
:overridden="overrides?.defaultDns"
/>
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading> <FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading>
@@ -46,14 +39,12 @@
v-model="data.defaultMtu" v-model="data.defaultMtu"
:label="$t('general.mtu')" :label="$t('general.mtu')"
:description="$t('admin.config.mtuDesc')" :description="$t('admin.config.mtuDesc')"
:overridden="overrides?.defaultMtu"
/> />
<FormNumberField <FormNumberField
id="defaultPersistentKeepalive" id="defaultPersistentKeepalive"
v-model="data.defaultPersistentKeepalive" v-model="data.defaultPersistentKeepalive"
:label="$t('general.persistentKeepalive')" :label="$t('general.persistentKeepalive')"
:description="$t('admin.config.persistentKeepaliveDesc')" :description="$t('admin.config.persistentKeepaliveDesc')"
:overridden="overrides?.defaultPersistentKeepalive"
/> />
</FormGroup> </FormGroup>
<FormGroup v-if="globalStore.information?.isAwg"> <FormGroup v-if="globalStore.information?.isAwg">
@@ -127,19 +118,14 @@ const { data: _data, refresh } = await useFetch(`/api/admin/userconfig`, {
method: 'get', method: 'get',
}); });
const { data: overridesData } = await useFetch(`/api/admin/overrides`, {
method: 'get',
});
const overrides = computed(() => overridesData.value?.userConfig);
const data = toRef(_data.value); const data = toRef(_data.value);
const _submit = useSubmit( const _submit = useSubmit(
`/api/admin/userconfig`, (data) =>
{ $fetch(`/api/admin/userconfig`, {
method: 'post', method: 'post',
}, body: data,
}),
{ revert } { revert }
); );

Some files were not shown because too many files have changed in this diff Show More