mirror of
https://github.com/h44z/wg-portal.git
synced 2026-02-24 11:26:21 +00:00
Compare commits
1 Commits
default_pe
...
bulk_actio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4968dcd6c |
6
.github/workflows/docker-publish.yml
vendored
6
.github/workflows/docker-publish.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Upload binaries
|
- name: Upload binaries
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: binaries
|
name: binaries
|
||||||
path: binaries/wg-portal_linux*
|
path: binaries/wg-portal_linux*
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Download binaries
|
- name: Download binaries
|
||||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: binaries
|
name: binaries
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func main() {
|
|||||||
rawDb, err := adapters.NewDatabase(cfg.Database)
|
rawDb, err := adapters.NewDatabase(cfg.Database)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
database, err := adapters.NewSqlRepository(rawDb, cfg)
|
database, err := adapters.NewSqlRepository(rawDb)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
wireGuard, err := wireguard.NewControllerManager(cfg)
|
wireGuard, err := wireguard.NewControllerManager(cfg)
|
||||||
|
|||||||
@@ -157,14 +157,12 @@ More advanced options are found in the subsequent `Advanced` section.
|
|||||||
### `create_default_peer`
|
### `create_default_peer`
|
||||||
- **Default:** `false`
|
- **Default:** `false`
|
||||||
- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER`
|
- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER`
|
||||||
- **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for all server interfaces where the "Create default peer" flag is set.
|
- **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for **all** server interfaces.
|
||||||
- **Important:** This option is only effective for interfaces where the "Create default peer" flag is set (via the UI).
|
|
||||||
|
|
||||||
### `create_default_peer_on_creation`
|
### `create_default_peer_on_creation`
|
||||||
- **Default:** `false`
|
- **Default:** `false`
|
||||||
- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION`
|
- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION`
|
||||||
- **Description:** If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for all server interfaces where the "Create default peer" flag is set.
|
- **Description:** If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for **all** server interfaces.
|
||||||
- **Important:** This option requires [create_default_peer](#create_default_peer) to be enabled.
|
|
||||||
|
|
||||||
### `re_enable_peer_after_user_enable`
|
### `re_enable_peer_after_user_enable`
|
||||||
- **Default:** `true`
|
- **Default:** `true`
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ Make sure that you download the correct binary for your architecture. The availa
|
|||||||
- `wg-portal_linux_arm64` - Linux ARM 64-bit
|
- `wg-portal_linux_arm64` - Linux ARM 64-bit
|
||||||
- `wg-portal_linux_arm_v7` - Linux ARM 32-bit
|
- `wg-portal_linux_arm_v7` - Linux ARM 32-bit
|
||||||
|
|
||||||
### Released versions
|
|
||||||
|
|
||||||
To download a specific version, replace `${WG_PORTAL_VERSION}` with the desired version (or set an environment variable).
|
|
||||||
All official release versions can be found on the [GitHub Releases Page](https://github.com/h44z/wg-portal/releases).
|
|
||||||
|
|
||||||
With `curl`:
|
With `curl`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -32,74 +27,16 @@ with `gh cli`:
|
|||||||
gh release download ${WG_PORTAL_VERSION} --repo h44z/wg-portal --output wg-portal --pattern '*amd64'
|
gh release download ${WG_PORTAL_VERSION} --repo h44z/wg-portal --output wg-portal --pattern '*amd64'
|
||||||
```
|
```
|
||||||
|
|
||||||
The downloaded file will be named `wg-portal` and can be moved to a directory of your choice, see [Install](#install) for more information.
|
|
||||||
|
|
||||||
### Unreleased versions (master branch builds)
|
|
||||||
|
|
||||||
Unreleased versions can be fetched directly from the artifacts section of the [GitHub Workflow](https://github.com/h44z/wg-portal/actions/workflows/docker-publish.yml?query=branch%3Amaster).
|
|
||||||
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
The following command can be used to install the downloaded binary (`wg-portal`) to `/opt/wg-portal/wg-portal`. It ensures that the binary is executable.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo mkdir -p /opt/wg-portal
|
sudo mkdir -p /opt/wg-portal
|
||||||
sudo install wg-portal /opt/wg-portal/
|
sudo install wg-portal /opt/wg-portal/
|
||||||
```
|
```
|
||||||
|
|
||||||
To handle tasks such as restarting the service or configuring automatic startup, it is recommended to use a process manager like [systemd](https://systemd.io/).
|
## Unreleased versions (master branch builds)
|
||||||
Refer to [Systemd Service Setup](#systemd-service-setup) for instructions.
|
|
||||||
|
|
||||||
## Systemd Service Setup
|
Unreleased versions can be fetched directly from the artifacts section of the [GitHub Workflow](https://github.com/h44z/wg-portal/actions/workflows/docker-publish.yml?query=branch%3Amaster).
|
||||||
|
|
||||||
> **Note:** To run WireGuard Portal as systemd service, you need to download the binary for your architecture beforehand.
|
|
||||||
>
|
|
||||||
> The following examples assume that you downloaded the binary to `/opt/wg-portal/wg-portal`.
|
|
||||||
> The configuration file is expected to be located at `/opt/wg-portal/config.yml`.
|
|
||||||
|
|
||||||
To run WireGuard Portal as a systemd service, you can create a service unit file. The easiest way to do this is by using `systemctl edit`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo systemctl edit --force --full wg-portal.service
|
|
||||||
```
|
|
||||||
|
|
||||||
Paste the following content into the editor and adjust the variables to your needs:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[Unit]
|
|
||||||
Description=WireGuard Portal
|
|
||||||
ConditionPathExists=/opt/wg-portal/wg-portal
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
|
|
||||||
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=10
|
|
||||||
|
|
||||||
WorkingDirectory=/opt/wg-portal
|
|
||||||
Environment=WG_PORTAL_CONFIG=/opt/wg-portal/config.yml
|
|
||||||
ExecStart=/opt/wg-portal/wg-portal
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can create or modify the file manually in `/etc/systemd/system/wg-portal.service`.
|
|
||||||
For systemd to pick up the changes, you need to reload the daemon:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
```
|
|
||||||
|
|
||||||
After creating the service file, you can enable and start the service:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo systemctl enable --now wg-portal.service
|
|
||||||
```
|
|
||||||
|
|
||||||
To check status and log output, use: `sudo systemctl status wg-portal.service` or `sudo journalctl -u wg-portal.service`.
|
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
formData.value.Identifier = interfaces.Prepared.Identifier
|
formData.value.Identifier = interfaces.Prepared.Identifier
|
||||||
formData.value.DisplayName = interfaces.Prepared.DisplayName
|
formData.value.DisplayName = interfaces.Prepared.DisplayName
|
||||||
formData.value.Mode = interfaces.Prepared.Mode
|
formData.value.Mode = interfaces.Prepared.Mode
|
||||||
formData.value.CreateDefaultPeer = interfaces.Prepared.CreateDefaultPeer
|
|
||||||
formData.value.Backend = interfaces.Prepared.Backend
|
formData.value.Backend = interfaces.Prepared.Backend
|
||||||
|
|
||||||
formData.value.PublicKey = interfaces.Prepared.PublicKey
|
formData.value.PublicKey = interfaces.Prepared.PublicKey
|
||||||
@@ -123,7 +122,6 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
formData.value.Identifier = selectedInterface.value.Identifier
|
formData.value.Identifier = selectedInterface.value.Identifier
|
||||||
formData.value.DisplayName = selectedInterface.value.DisplayName
|
formData.value.DisplayName = selectedInterface.value.DisplayName
|
||||||
formData.value.Mode = selectedInterface.value.Mode
|
formData.value.Mode = selectedInterface.value.Mode
|
||||||
formData.value.CreateDefaultPeer = selectedInterface.value.CreateDefaultPeer
|
|
||||||
formData.value.Backend = selectedInterface.value.Backend
|
formData.value.Backend = selectedInterface.value.Backend
|
||||||
|
|
||||||
formData.value.PublicKey = selectedInterface.value.PublicKey
|
formData.value.PublicKey = selectedInterface.value.PublicKey
|
||||||
@@ -489,10 +487,6 @@ async function del() {
|
|||||||
<input v-model="formData.Disabled" class="form-check-input" type="checkbox">
|
<input v-model="formData.Disabled" class="form-check-input" type="checkbox">
|
||||||
<label class="form-check-label">{{ $t('modals.interface-edit.disabled.label') }}</label>
|
<label class="form-check-label">{{ $t('modals.interface-edit.disabled.label') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch" v-if="formData.Mode==='server' && settings.Setting('CreateDefaultPeer')">
|
|
||||||
<input v-model="formData.CreateDefaultPeer" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">{{ $t('modals.interface-edit.create-default-peer.label') }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check form-switch" v-if="formData.Backend==='local'">
|
<div class="form-check form-switch" v-if="formData.Backend==='local'">
|
||||||
<input v-model="formData.SaveConfig" checked="" class="form-check-input" type="checkbox">
|
<input v-model="formData.SaveConfig" checked="" class="form-check-input" type="checkbox">
|
||||||
<label class="form-check-label">{{ $t('modals.interface-edit.save-config.label') }}</label>
|
<label class="form-check-label">{{ $t('modals.interface-edit.save-config.label') }}</label>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ export function freshInterface() {
|
|||||||
Disabled: false,
|
Disabled: false,
|
||||||
DisplayName: "",
|
DisplayName: "",
|
||||||
Identifier: "",
|
Identifier: "",
|
||||||
CreateDefaultPeer: false,
|
|
||||||
Mode: "server",
|
Mode: "server",
|
||||||
Backend: "local",
|
Backend: "local",
|
||||||
|
|
||||||
|
|||||||
@@ -469,9 +469,6 @@
|
|||||||
"disabled": {
|
"disabled": {
|
||||||
"label": "Schnittstelle deaktiviert"
|
"label": "Schnittstelle deaktiviert"
|
||||||
},
|
},
|
||||||
"create-default-peer": {
|
|
||||||
"label": "Peer für neue Benutzer automatisch erstellen"
|
|
||||||
},
|
|
||||||
"save-config": {
|
"save-config": {
|
||||||
"label": "wg-quick Konfiguration automatisch speichern"
|
"label": "wg-quick Konfiguration automatisch speichern"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -469,9 +469,6 @@
|
|||||||
"disabled": {
|
"disabled": {
|
||||||
"label": "Interface Disabled"
|
"label": "Interface Disabled"
|
||||||
},
|
},
|
||||||
"create-default-peer": {
|
|
||||||
"label": "Create default peer for new users"
|
|
||||||
},
|
|
||||||
"save-config": {
|
"save-config": {
|
||||||
"label": "Automatically save wg-quick config"
|
"label": "Automatically save wg-quick config"
|
||||||
},
|
},
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -9,7 +9,7 @@ require (
|
|||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/go-ldap/ldap/v3 v3.4.12
|
github.com/go-ldap/ldap/v3 v3.4.12
|
||||||
github.com/go-pkgz/routegroup v1.6.0
|
github.com/go-pkgz/routegroup v1.6.0
|
||||||
github.com/go-playground/validator/v10 v10.30.1
|
github.com/go-playground/validator/v10 v10.28.0
|
||||||
github.com/go-webauthn/webauthn v0.15.0
|
github.com/go-webauthn/webauthn v0.15.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/prometheus-community/pro-bing v0.7.0
|
github.com/prometheus-community/pro-bing v0.7.0
|
||||||
@@ -41,7 +41,7 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.11 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -50,8 +50,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||||
@@ -97,8 +97,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SchemaVersion describes the current database schema version. It must be incremented if a manual migration is needed.
|
// SchemaVersion describes the current database schema version. It must be incremented if a manual migration is needed.
|
||||||
var SchemaVersion uint64 = 2
|
var SchemaVersion uint64 = 1
|
||||||
|
|
||||||
// SysStat stores the current database schema version and the timestamp when it was applied.
|
// SysStat stores the current database schema version and the timestamp when it was applied.
|
||||||
type SysStat struct {
|
type SysStat struct {
|
||||||
@@ -180,14 +180,12 @@ func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
|||||||
// Currently, it supports MySQL, SQLite, Microsoft SQL and Postgresql database systems.
|
// Currently, it supports MySQL, SQLite, Microsoft SQL and Postgresql database systems.
|
||||||
type SqlRepo struct {
|
type SqlRepo struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
cfg *config.Config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSqlRepository creates a new SqlRepo instance.
|
// NewSqlRepository creates a new SqlRepo instance.
|
||||||
func NewSqlRepository(db *gorm.DB, cfg *config.Config) (*SqlRepo, error) {
|
func NewSqlRepository(db *gorm.DB) (*SqlRepo, error) {
|
||||||
repo := &SqlRepo{
|
repo := &SqlRepo{
|
||||||
db: db,
|
db: db,
|
||||||
cfg: cfg,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.preCheck(); err != nil {
|
if err := repo.preCheck(); err != nil {
|
||||||
@@ -234,9 +232,7 @@ func (r *SqlRepo) migrate() error {
|
|||||||
slog.Debug("running migration: audit data", "result", r.db.AutoMigrate(&domain.AuditEntry{}))
|
slog.Debug("running migration: audit data", "result", r.db.AutoMigrate(&domain.AuditEntry{}))
|
||||||
|
|
||||||
existingSysStat := SysStat{}
|
existingSysStat := SysStat{}
|
||||||
r.db.Order("schema_version desc").First(&existingSysStat) // get latest version
|
r.db.Where("schema_version = ?", SchemaVersion).First(&existingSysStat)
|
||||||
|
|
||||||
// Migration: 0 --> 1
|
|
||||||
if existingSysStat.SchemaVersion == 0 {
|
if existingSysStat.SchemaVersion == 0 {
|
||||||
sysStat := SysStat{
|
sysStat := SysStat{
|
||||||
MigratedAt: time.Now(),
|
MigratedAt: time.Now(),
|
||||||
@@ -248,27 +244,6 @@ func (r *SqlRepo) migrate() error {
|
|||||||
slog.Debug("sys-stat entry written", "schema_version", SchemaVersion)
|
slog.Debug("sys-stat entry written", "schema_version", SchemaVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration: 1 --> 2
|
|
||||||
if existingSysStat.SchemaVersion == 1 {
|
|
||||||
// Preserve existing behavior for installations that had default-peer-creation enabled.
|
|
||||||
if r.cfg.Core.CreateDefaultPeer {
|
|
||||||
err := r.db.Model(&domain.Interface{}).
|
|
||||||
Where("type = ?", domain.InterfaceTypeServer).
|
|
||||||
Update("create_default_peer", true).Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to migrate interface flags for schema version %d: %w", SchemaVersion, err)
|
|
||||||
}
|
|
||||||
slog.Debug("migrated interface create_default_peer flags", "schema_version", SchemaVersion)
|
|
||||||
}
|
|
||||||
sysStat := SysStat{
|
|
||||||
MigratedAt: time.Now(),
|
|
||||||
SchemaVersion: SchemaVersion,
|
|
||||||
}
|
|
||||||
if err := r.db.Create(&sysStat).Error; err != nil {
|
|
||||||
return fmt.Errorf("failed to write sysstat entry for schema version %d: %w", SchemaVersion, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,6 @@ func (e ConfigEndpoint) handleSettingsGet() http.HandlerFunc {
|
|||||||
MinPasswordLength: e.cfg.Auth.MinPasswordLength,
|
MinPasswordLength: e.cfg.Auth.MinPasswordLength,
|
||||||
AvailableBackends: controllerFn(),
|
AvailableBackends: controllerFn(),
|
||||||
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
|
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
|
||||||
CreateDefaultPeer: e.cfg.Core.CreateDefaultPeer,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ type Settings struct {
|
|||||||
MinPasswordLength int `json:"MinPasswordLength"`
|
MinPasswordLength int `json:"MinPasswordLength"`
|
||||||
AvailableBackends []SettingsBackendNames `json:"AvailableBackends"`
|
AvailableBackends []SettingsBackendNames `json:"AvailableBackends"`
|
||||||
LoginFormVisible bool `json:"LoginFormVisible"`
|
LoginFormVisible bool `json:"LoginFormVisible"`
|
||||||
CreateDefaultPeer bool `json:"CreateDefaultPeer"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingsBackendNames struct {
|
type SettingsBackendNames struct {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type Interface struct {
|
|||||||
Disabled bool `json:"Disabled"` // flag that specifies if the interface is enabled (up) or not (down)
|
Disabled bool `json:"Disabled"` // flag that specifies if the interface is enabled (up) or not (down)
|
||||||
DisabledReason string `json:"DisabledReason"` // the reason why the interface has been disabled
|
DisabledReason string `json:"DisabledReason"` // the reason why the interface has been disabled
|
||||||
SaveConfig bool `json:"SaveConfig"` // automatically persist config changes to the wgX.conf file
|
SaveConfig bool `json:"SaveConfig"` // automatically persist config changes to the wgX.conf file
|
||||||
CreateDefaultPeer bool `json:"CreateDefaultPeer"` // if true, default peers will be created for this interface
|
|
||||||
|
|
||||||
ListenPort int `json:"ListenPort"` // the listening port, for example: 51820
|
ListenPort int `json:"ListenPort"` // the listening port, for example: 51820
|
||||||
Addresses []string `json:"Addresses"` // the interface ip addresses
|
Addresses []string `json:"Addresses"` // the interface ip addresses
|
||||||
@@ -66,7 +65,6 @@ func NewInterface(src *domain.Interface, peers []domain.Peer) *Interface {
|
|||||||
Disabled: src.IsDisabled(),
|
Disabled: src.IsDisabled(),
|
||||||
DisabledReason: src.DisabledReason,
|
DisabledReason: src.DisabledReason,
|
||||||
SaveConfig: src.SaveConfig,
|
SaveConfig: src.SaveConfig,
|
||||||
CreateDefaultPeer: src.CreateDefaultPeer,
|
|
||||||
ListenPort: src.ListenPort,
|
ListenPort: src.ListenPort,
|
||||||
Addresses: domain.CidrsToStringSlice(src.Addresses),
|
Addresses: domain.CidrsToStringSlice(src.Addresses),
|
||||||
Dns: internal.SliceString(src.DnsStr),
|
Dns: internal.SliceString(src.DnsStr),
|
||||||
@@ -153,7 +151,6 @@ func NewDomainInterface(src *Interface) *domain.Interface {
|
|||||||
PreDown: src.PreDown,
|
PreDown: src.PreDown,
|
||||||
PostDown: src.PostDown,
|
PostDown: src.PostDown,
|
||||||
SaveConfig: src.SaveConfig,
|
SaveConfig: src.SaveConfig,
|
||||||
CreateDefaultPeer: src.CreateDefaultPeer,
|
|
||||||
DisplayName: src.DisplayName,
|
DisplayName: src.DisplayName,
|
||||||
Type: domain.InterfaceType(src.Mode),
|
Type: domain.InterfaceType(src.Mode),
|
||||||
Backend: domain.InterfaceBackend(src.Backend),
|
Backend: domain.InterfaceBackend(src.Backend),
|
||||||
|
|||||||
@@ -374,7 +374,6 @@ func (m Manager) PrepareInterface(ctx context.Context) (*domain.Interface, error
|
|||||||
SaveConfig: m.cfg.Advanced.ConfigStoragePath != "",
|
SaveConfig: m.cfg.Advanced.ConfigStoragePath != "",
|
||||||
DisplayName: string(id),
|
DisplayName: string(id),
|
||||||
Type: domain.InterfaceTypeServer,
|
Type: domain.InterfaceTypeServer,
|
||||||
CreateDefaultPeer: m.cfg.Core.CreateDefaultPeer,
|
|
||||||
DriverType: "",
|
DriverType: "",
|
||||||
Disabled: nil,
|
Disabled: nil,
|
||||||
DisabledReason: "",
|
DisabledReason: "",
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ func (m Manager) CreateDefaultPeer(ctx context.Context, userId domain.UserIdenti
|
|||||||
continue // only create default peers for server interfaces
|
continue // only create default peers for server interfaces
|
||||||
}
|
}
|
||||||
|
|
||||||
if !iface.CreateDefaultPeer {
|
|
||||||
continue // only create default peers if the interface flag is set
|
|
||||||
}
|
|
||||||
|
|
||||||
peerAlreadyCreated := slices.ContainsFunc(userPeers, func(peer domain.Peer) bool {
|
peerAlreadyCreated := slices.ContainsFunc(userPeers, func(peer domain.Peer) bool {
|
||||||
return peer.InterfaceIdentifier == iface.Identifier
|
return peer.InterfaceIdentifier == iface.Identifier
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -78,12 +78,7 @@ func (f *mockDB) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceId
|
|||||||
func (f *mockDB) GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifier) ([]domain.PeerStatus, error) {
|
func (f *mockDB) GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifier) ([]domain.PeerStatus, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (f *mockDB) GetAllInterfaces(ctx context.Context) ([]domain.Interface, error) {
|
func (f *mockDB) GetAllInterfaces(ctx context.Context) ([]domain.Interface, error) { return nil, nil }
|
||||||
if f.iface != nil {
|
|
||||||
return []domain.Interface{*f.iface}, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
func (f *mockDB) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIdentifier][]domain.Cidr, error) {
|
func (f *mockDB) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIdentifier][]domain.Cidr, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -197,58 +192,3 @@ func TestCreatePeer_SetsIdentifier_FromPublicKey(t *testing.T) {
|
|||||||
t.Fatalf("expected peer with identifier %q to be saved in DB", expectedId)
|
t.Fatalf("expected peer with identifier %q to be saved in DB", expectedId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDefaultPeer_RespectsInterfaceFlag(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
cfg := &config.Config{}
|
|
||||||
cfg.Core.CreateDefaultPeer = true
|
|
||||||
|
|
||||||
bus := &mockBus{}
|
|
||||||
ctrlMgr := &ControllerManager{
|
|
||||||
controllers: map[domain.InterfaceBackend]backendInstance{
|
|
||||||
config.LocalBackendName: {Implementation: &mockController{}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
db := &mockDB{
|
|
||||||
iface: &domain.Interface{
|
|
||||||
Identifier: "wg0",
|
|
||||||
Type: domain.InterfaceTypeServer,
|
|
||||||
CreateDefaultPeer: false, // Flag is disabled!
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
m := Manager{
|
|
||||||
cfg: cfg,
|
|
||||||
bus: bus,
|
|
||||||
db: db,
|
|
||||||
wg: ctrlMgr,
|
|
||||||
}
|
|
||||||
|
|
||||||
userId := domain.UserIdentifier("user@example.com")
|
|
||||||
ctx := domain.SetUserInfo(context.Background(), &domain.ContextUserInfo{Id: userId, IsAdmin: true})
|
|
||||||
|
|
||||||
// Act
|
|
||||||
err := m.CreateDefaultPeer(ctx, userId)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateDefaultPeer returned error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(db.savedPeers) != 0 {
|
|
||||||
t.Fatalf("expected no peers to be created because interface flag is false, but got %d", len(db.savedPeers))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now enable the flag and try again
|
|
||||||
db.iface.CreateDefaultPeer = true
|
|
||||||
err = m.CreateDefaultPeer(ctx, userId)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateDefaultPeer returned error after enabling flag: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(db.savedPeers) != 1 {
|
|
||||||
t.Fatalf("expected 1 peer to be created because interface flag is true, but got %d", len(db.savedPeers))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ type Interface struct {
|
|||||||
// WG Portal specific
|
// WG Portal specific
|
||||||
DisplayName string // a nice display name/ description for the interface
|
DisplayName string // a nice display name/ description for the interface
|
||||||
Type InterfaceType // the interface type, either InterfaceTypeServer or InterfaceTypeClient
|
Type InterfaceType // the interface type, either InterfaceTypeServer or InterfaceTypeClient
|
||||||
CreateDefaultPeer bool // if true, default peers will be created for this interface
|
|
||||||
Backend InterfaceBackend // the backend that is used to manage the interface (wgctrl, mikrotik, ...)
|
Backend InterfaceBackend // the backend that is used to manage the interface (wgctrl, mikrotik, ...)
|
||||||
DriverType string // the interface driver type (linux, software, ...)
|
DriverType string // the interface driver type (linux, software, ...)
|
||||||
Disabled *time.Time `gorm:"index"` // flag that specifies if the interface is enabled (up) or not (down)
|
Disabled *time.Time `gorm:"index"` // flag that specifies if the interface is enabled (up) or not (down)
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ nav:
|
|||||||
- Reverse Proxy (HTTPS): documentation/getting-started/reverse-proxy.md
|
- Reverse Proxy (HTTPS): documentation/getting-started/reverse-proxy.md
|
||||||
- Configuration:
|
- Configuration:
|
||||||
- Overview: documentation/configuration/overview.md
|
- Overview: documentation/configuration/overview.md
|
||||||
|
- Mail templates: documentation/configuration/mail-templates.md
|
||||||
- Examples: documentation/configuration/examples.md
|
- Examples: documentation/configuration/examples.md
|
||||||
- Usage:
|
- Usage:
|
||||||
- General: documentation/usage/general.md
|
- General: documentation/usage/general.md
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ After=network.target
|
|||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User=root
|
||||||
Group=root
|
Group=root
|
||||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
|
|
||||||
|
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
|
|
||||||
WorkingDirectory=/opt/wg-portal
|
WorkingDirectory=/opt/wg-portal
|
||||||
Environment=WG_PORTAL_CONFIG=/opt/wg-portal/config.yml
|
|
||||||
ExecStart=/opt/wg-portal/wg-portal-amd64
|
ExecStart=/opt/wg-portal/wg-portal-amd64
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
Reference in New Issue
Block a user