feat: config by environment variables (#570)
Some checks failed
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled

* feat: config by environment variables without config file

Signed-off-by: Isak Wertwein <isak.wertwein@gmail.com>

* string slice by environment variable

Signed-off-by: Isak Wertwein <isak.wertwein@gmail.com>

---------

Signed-off-by: Isak Wertwein <isak.wertwein@gmail.com>
This commit is contained in:
Isak Wertwein
2025-11-16 18:33:25 +01:00
committed by GitHub
parent 8bc4990441
commit 8f25bef050
2 changed files with 216 additions and 62 deletions

View File

@@ -127,51 +127,63 @@ More advanced options are found in the subsequent `Advanced` section.
### `admin_user` ### `admin_user`
- **Default:** `admin@wgportal.local` - **Default:** `admin@wgportal.local`
- **Environment Variable:** `WG_PORTAL_CORE_ADMIN_USER`
- **Description:** The administrator user. This user will be created as a default admin if it does not yet exist. - **Description:** The administrator user. This user will be created as a default admin if it does not yet exist.
### `admin_password` ### `admin_password`
- **Default:** `wgportal-default` - **Default:** `wgportal-default`
- **Environment Variable:** `WG_PORTAL_CORE_ADMIN_PASSWORD`
- **Description:** The administrator password. The default password should be changed immediately! - **Description:** The administrator password. The default password should be changed immediately!
- **Important:** The password should be strong and secure. The minimum password length is specified in [auth.min_password_length](#min_password_length). By default, it is 16 characters. - **Important:** The password should be strong and secure. The minimum password length is specified in [auth.min_password_length](#min_password_length). By default, it is 16 characters.
### `disable_admin_user` ### `disable_admin_user`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_CORE_DISABLE_ADMIN_USER`
- **Description:** If `true`, no admin user is created. This is useful if you plan to manage users exclusively through external authentication providers such as LDAP or OAuth. - **Description:** If `true`, no admin user is created. This is useful if you plan to manage users exclusively through external authentication providers such as LDAP or OAuth.
### `admin_api_token` ### `admin_api_token`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_CORE_ADMIN_API_TOKEN`
- **Description:** An API token for the admin user. If a token is provided, the REST API can be accessed using this token. If empty, the API is initially disabled for the admin user. - **Description:** An API token for the admin user. If a token is provided, the REST API can be accessed using this token. If empty, the API is initially disabled for the admin user.
### `editable_keys` ### `editable_keys`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_CORE_EDITABLE_KEYS`
- **Description:** Allow editing of WireGuard key-pairs directly in the UI. - **Description:** Allow editing of WireGuard key-pairs directly in the UI.
### `create_default_peer` ### `create_default_peer`
- **Default:** `false` - **Default:** `false`
- **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. - **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for **all** server interfaces.
### `create_default_peer_on_creation` ### `create_default_peer_on_creation`
- **Default:** `false` - **Default:** `false`
- **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. - **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.
### `re_enable_peer_after_user_enable` ### `re_enable_peer_after_user_enable`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_CORE_RE_ENABLE_PEER_AFTER_USER_ENABLE`
- **Description:** Re-enable all peers that were previously disabled if the associated user is re-enabled. - **Description:** Re-enable all peers that were previously disabled if the associated user is re-enabled.
### `delete_peer_after_user_deleted` ### `delete_peer_after_user_deleted`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_CORE_DELETE_PEER_AFTER_USER_DELETED`
- **Description:** If a user is deleted, remove all linked peers. Otherwise, peers remain but are disabled. - **Description:** If a user is deleted, remove all linked peers. Otherwise, peers remain but are disabled.
### `self_provisioning_allowed` ### `self_provisioning_allowed`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_CORE_SELF_PROVISIONING_ALLOWED`
- **Description:** Allow registered (non-admin) users to self-provision peers from their profile page. - **Description:** Allow registered (non-admin) users to self-provision peers from their profile page.
### `import_existing` ### `import_existing`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_CORE_IMPORT_EXISTING`
- **Description:** On startup, import existing WireGuard interfaces and peers into WireGuard Portal. - **Description:** On startup, import existing WireGuard interfaces and peers into WireGuard Portal.
### `restore_state` ### `restore_state`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_CORE_RESTORE_STATE`
- **Description:** Restore the WireGuard interface states (up/down) that existed before WireGuard Portal started. - **Description:** Restore the WireGuard interface states (up/down) that existed before WireGuard Portal started.
--- ---
@@ -188,11 +200,14 @@ The current MikroTik backend is in **BETA** and may not support all features.
### `local_resolvconf_prefix` ### `local_resolvconf_prefix`
- **Default:** `tun.` - **Default:** `tun.`
- **Environment Variable:** `WG_PORTAL_BACKEND_LOCAL_RESOLVCONF_PREFIX`
- **Description:** Interface name prefix for WireGuard interfaces on the local system which is used to configure DNS servers with *resolvconf*. - **Description:** Interface name prefix for WireGuard interfaces on the local system which is used to configure DNS servers with *resolvconf*.
It depends on the *resolvconf* implementation you are using, most use a prefix of `tun.`, but some have an empty prefix (e.g., systemd). It depends on the *resolvconf* implementation you are using, most use a prefix of `tun.`, but some have an empty prefix (e.g., systemd).
### `ignored_local_interfaces` ### `ignored_local_interfaces`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_BACKEND_IGNORED_LOCAL_INTERFACES`
(comma-separated values)
- **Description:** A list of interface names to exclude when enumerating local interfaces. - **Description:** A list of interface names to exclude when enumerating local interfaces.
This is useful if you want to prevent certain interfaces from being imported from the local system. This is useful if you want to prevent certain interfaces from being imported from the local system.
@@ -256,54 +271,67 @@ Additional or more specialized configuration options for logging and interface c
### `log_level` ### `log_level`
- **Default:** `info` - **Default:** `info`
- **Environment Variable:** `WG_PORTAL_ADVANCED_LOG_LEVEL`
- **Description:** The log level used by the application. Valid options are: `trace`, `debug`, `info`, `warn`, `error`. - **Description:** The log level used by the application. Valid options are: `trace`, `debug`, `info`, `warn`, `error`.
### `log_pretty` ### `log_pretty`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_ADVANCED_LOG_PRETTY`
- **Description:** If `true`, log messages are colorized and formatted for readability (pretty-print). - **Description:** If `true`, log messages are colorized and formatted for readability (pretty-print).
### `log_json` ### `log_json`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_ADVANCED_LOG_JSON`
- **Description:** If `true`, log messages are structured in JSON format. - **Description:** If `true`, log messages are structured in JSON format.
### `start_listen_port` ### `start_listen_port`
- **Default:** `51820` - **Default:** `51820`
- **Environment Variable:** `WG_PORTAL_ADVANCED_START_LISTEN_PORT`
- **Description:** The first port to use when automatically creating new WireGuard interfaces. - **Description:** The first port to use when automatically creating new WireGuard interfaces.
### `start_cidr_v4` ### `start_cidr_v4`
- **Default:** `10.11.12.0/24` - **Default:** `10.11.12.0/24`
- **Environment Variable:** `WG_PORTAL_ADVANCED_START_CIDR_V4`
- **Description:** The initial IPv4 subnet to use when automatically creating new WireGuard interfaces. - **Description:** The initial IPv4 subnet to use when automatically creating new WireGuard interfaces.
### `start_cidr_v6` ### `start_cidr_v6`
- **Default:** `fdfd:d3ad:c0de:1234::0/64` - **Default:** `fdfd:d3ad:c0de:1234::0/64`
- **Environment Variable:** `WG_PORTAL_ADVANCED_START_CIDR_V6`
- **Description:** The initial IPv6 subnet to use when automatically creating new WireGuard interfaces. - **Description:** The initial IPv6 subnet to use when automatically creating new WireGuard interfaces.
### `use_ip_v6` ### `use_ip_v6`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_ADVANCED_USE_IP_V6`
- **Description:** Enable or disable IPv6 support. - **Description:** Enable or disable IPv6 support.
### `config_storage_path` ### `config_storage_path`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_ADVANCED_CONFIG_STORAGE_PATH`
- **Description:** Path to a directory where `wg-quick` style configuration files will be stored (if you need local filesystem configs). - **Description:** Path to a directory where `wg-quick` style configuration files will be stored (if you need local filesystem configs).
### `expiry_check_interval` ### `expiry_check_interval`
- **Default:** `15m` - **Default:** `15m`
- **Environment Variable:** `WG_PORTAL_ADVANCED_EXPIRY_CHECK_INTERVAL`
- **Description:** Interval after which existing peers are checked if they are expired. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). - **Description:** Interval after which existing peers are checked if they are expired. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
### `rule_prio_offset` ### `rule_prio_offset`
- **Default:** `20000` - **Default:** `20000`
- **Environment Variable:** `WG_PORTAL_ADVANCED_RULE_PRIO_OFFSET`
- **Description:** Offset for IP route rule priorities when configuring routing. - **Description:** Offset for IP route rule priorities when configuring routing.
### `route_table_offset` ### `route_table_offset`
- **Default:** `20000` - **Default:** `20000`
- **Environment Variable:** `WG_PORTAL_ADVANCED_ROUTE_TABLE_OFFSET`
- **Description:** Offset for IP route table IDs when configuring routing. - **Description:** Offset for IP route table IDs when configuring routing.
### `api_admin_only` ### `api_admin_only`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_ADVANCED_API_ADMIN_ONLY`
- **Description:** If `true`, the public REST API is accessible only to admin users. The API docs live at [`/api/v1/doc.html`](../rest-api/api-doc.md). - **Description:** If `true`, the public REST API is accessible only to admin users. The API docs live at [`/api/v1/doc.html`](../rest-api/api-doc.md).
### `limit_additional_user_peers` ### `limit_additional_user_peers`
- **Default:** `0` - **Default:** `0`
- **Environment Variable:** `WG_PORTAL_ADVANCED_LIMIT_ADDITIONAL_USER_PEERS`
- **Description:** Limit additional peers a normal user can create. `0` means unlimited. - **Description:** Limit additional peers a normal user can create. `0` means unlimited.
--- ---
@@ -317,18 +345,22 @@ If sensitive values (like private keys) should be stored in an encrypted format,
### `debug` ### `debug`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_DATABASE_DEBUG`
- **Description:** If `true`, logs all database statements (verbose). - **Description:** If `true`, logs all database statements (verbose).
### `slow_query_threshold` ### `slow_query_threshold`
- **Default:** "0" - **Default:** "0"
- **Environment Variable:** `WG_PORTAL_DATABASE_SLOW_QUERY_THRESHOLD`
- **Description:** A time threshold (e.g., `100ms`) above which queries are considered slow and logged as warnings. If zero, slow query logging is disabled. Format uses `s`, `ms` for seconds, milliseconds, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). The value must be a string. - **Description:** A time threshold (e.g., `100ms`) above which queries are considered slow and logged as warnings. If zero, slow query logging is disabled. Format uses `s`, `ms` for seconds, milliseconds, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). The value must be a string.
### `type` ### `type`
- **Default:** `sqlite` - **Default:** `sqlite`
- **Environment Variable:** `WG_PORTAL_DATABASE_TYPE`
- **Description:** The database type. Valid options: `sqlite`, `mssql`, `mysql`, `postgres`. - **Description:** The database type. Valid options: `sqlite`, `mssql`, `mysql`, `postgres`.
### `dsn` ### `dsn`
- **Default:** `data/sqlite.db` - **Default:** `data/sqlite.db`
- **Environment Variable:** `WG_PORTAL_DATABASE_DSN`
- **Description:** The Data Source Name (DSN) for connecting to the database. - **Description:** The Data Source Name (DSN) for connecting to the database.
For example: For example:
```text ```text
@@ -337,6 +369,7 @@ If sensitive values (like private keys) should be stored in an encrypted format,
### `encryption_passphrase` ### `encryption_passphrase`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_DATABASE_ENCRYPTION_PASSPHRASE`
- **Description:** Passphrase for encrypting sensitive values such as private keys in the database. Encryption is only applied if this passphrase is set. - **Description:** Passphrase for encrypting sensitive values such as private keys in the database. Encryption is only applied if this passphrase is set.
**Important:** Once you enable encryption by setting this passphrase, you cannot disable it or change it afterward. **Important:** Once you enable encryption by setting this passphrase, you cannot disable it or change it afterward.
New or updated records will be encrypted; existing data remains in plaintext until its next modified. New or updated records will be encrypted; existing data remains in plaintext until its next modified.
@@ -349,38 +382,47 @@ Controls how WireGuard Portal collects and reports usage statistics, including p
### `use_ping_checks` ### `use_ping_checks`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_STATISTICS_USE_PING_CHECKS`
- **Description:** Enable periodic ping checks to verify that peers remain responsive. - **Description:** Enable periodic ping checks to verify that peers remain responsive.
### `ping_check_workers` ### `ping_check_workers`
- **Default:** `10` - **Default:** `10`
- **Environment Variable:** `WG_PORTAL_STATISTICS_PING_CHECK_WORKERS`
- **Description:** Number of parallel worker processes for ping checks. - **Description:** Number of parallel worker processes for ping checks.
### `ping_unprivileged` ### `ping_unprivileged`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_STATISTICS_PING_UNPRIVILEGED`
- **Description:** If `false`, ping checks run without root privileges. This is currently considered BETA. - **Description:** If `false`, ping checks run without root privileges. This is currently considered BETA.
### `ping_check_interval` ### `ping_check_interval`
- **Default:** `1m` - **Default:** `1m`
- **Environment Variable:** `WG_PORTAL_STATISTICS_PING_CHECK_INTERVAL`
- **Description:** Interval between consecutive ping checks for all peers. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). - **Description:** Interval between consecutive ping checks for all peers. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
### `data_collection_interval` ### `data_collection_interval`
- **Default:** `1m` - **Default:** `1m`
- **Environment Variable:** `WG_PORTAL_STATISTICS_DATA_COLLECTION_INTERVAL`
- **Description:** Interval between data collection cycles (bytes sent/received, handshake times, etc.). Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). - **Description:** Interval between data collection cycles (bytes sent/received, handshake times, etc.). Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
### `collect_interface_data` ### `collect_interface_data`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_STATISTICS_COLLECT_INTERFACE_DATA`
- **Description:** If `true`, collects interface-level data (bytes in/out) for monitoring and statistics. - **Description:** If `true`, collects interface-level data (bytes in/out) for monitoring and statistics.
### `collect_peer_data` ### `collect_peer_data`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_STATISTICS_COLLECT_PEER_DATA`
- **Description:** If `true`, collects peer-level data (bytes, last handshake, endpoint, etc.). - **Description:** If `true`, collects peer-level data (bytes, last handshake, endpoint, etc.).
### `collect_audit_data` ### `collect_audit_data`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_STATISTICS_COLLECT_AUDIT_DATA`
- **Description:** If `true`, logs certain portal events (such as user logins) to the database. - **Description:** If `true`, logs certain portal events (such as user logins) to the database.
### `listening_address` ### `listening_address`
- **Default:** `:8787` - **Default:** `:8787`
- **Environment Variable:** `WG_PORTAL_STATISTICS_LISTENING_ADDRESS`
- **Description:** Address and port for the integrated Prometheus metric server (e.g., `:8787` or `127.0.0.1:8787`). - **Description:** Address and port for the integrated Prometheus metric server (e.g., `:8787` or `127.0.0.1:8787`).
--- ---
@@ -393,45 +435,55 @@ To send emails to all peers that have a valid email-address as user-identifier,
### `host` ### `host`
- **Default:** `127.0.0.1` - **Default:** `127.0.0.1`
- **Environment Variable:** `WG_PORTAL_MAIL_HOST`
- **Description:** Hostname or IP of the SMTP server. - **Description:** Hostname or IP of the SMTP server.
### `port` ### `port`
- **Default:** `25` - **Default:** `25`
- **Environment Variable:** `WG_PORTAL_MAIL_PORT`
- **Description:** Port number for the SMTP server. - **Description:** Port number for the SMTP server.
### `encryption` ### `encryption`
- **Default:** `none` - **Default:** `none`
- **Environment Variable:** `WG_PORTAL_MAIL_ENCRYPTION`
- **Description:** SMTP encryption type. Valid values: `none`, `tls`, `starttls`. - **Description:** SMTP encryption type. Valid values: `none`, `tls`, `starttls`.
### `cert_validation` ### `cert_validation`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_MAIL_CERT_VALIDATION`
- **Description:** If `true`, validate the SMTP server certificate (relevant if `encryption` = `tls`). - **Description:** If `true`, validate the SMTP server certificate (relevant if `encryption` = `tls`).
### `username` ### `username`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_MAIL_USERNAME`
- **Description:** Optional SMTP username for authentication. - **Description:** Optional SMTP username for authentication.
### `password` ### `password`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_MAIL_PASSWORD`
- **Description:** Optional SMTP password for authentication. - **Description:** Optional SMTP password for authentication.
### `auth_type` ### `auth_type`
- **Default:** `plain` - **Default:** `plain`
- **Environment Variable:** `WG_PORTAL_MAIL_AUTH_TYPE`
- **Description:** SMTP authentication type. Valid values: `plain`, `login`, `crammd5`. - **Description:** SMTP authentication type. Valid values: `plain`, `login`, `crammd5`.
### `from` ### `from`
- **Default:** `Wireguard Portal <noreply@wireguard.local>` - **Default:** `Wireguard Portal <noreply@wireguard.local>`
- **Environment Variable:** `WG_PORTAL_MAIL_FROM`
- **Description:** The default "From" address when sending emails. - **Description:** The default "From" address when sending emails.
### `link_only` ### `link_only`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_MAIL_LINK_ONLY`
- **Description:** If `true`, emails only contain a link to WireGuard Portal, rather than attaching the full configuration. - **Description:** If `true`, emails only contain a link to WireGuard Portal, rather than attaching the full configuration.
### `allow_peer_email` ### `allow_peer_email`
- **Default:** `false` - **Default:** `false`
- **Description:** If `true`, and a peer has no valid user record linked, but the user-identifier of the peer is a valid email address, emails will be sent to that email address. - **Environment Variable:** `WG_PORTAL_MAIL_ALLOW_PEER_EMAIL`
If false, and the peer has no valid user record linked, emails will not be sent. - **Description:** If `true`, and a peer has no valid user record linked, but the user-identifier of the peer is a valid email address, emails will be sent to that email address.
If a peer has linked a valid user, the email address is always taken from the user record. If false, and the peer has no valid user record linked, emails will not be sent.
If a peer has linked a valid user, the email address is always taken from the user record.
--- ---
@@ -444,12 +496,14 @@ Some core authentication options are shared across all providers, while others a
### `min_password_length` ### `min_password_length`
- **Default:** `16` - **Default:** `16`
- **Environment Variable:** `WG_PORTAL_AUTH_MIN_PASSWORD_LENGTH`
- **Description:** Minimum password length for local authentication. This is not enforced for LDAP authentication. - **Description:** Minimum password length for local authentication. This is not enforced for LDAP authentication.
The default admin password strength is also enforced by this setting. The default admin password strength is also enforced by this setting.
- **Important:** The password should be strong and secure. It is recommended to use a password with at least 16 characters, including uppercase and lowercase letters, numbers, and special characters. - **Important:** The password should be strong and secure. It is recommended to use a password with at least 16 characters, including uppercase and lowercase letters, numbers, and special characters.
### `hide_login_form` ### `hide_login_form`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_AUTH_HIDE_LOGIN_FORM`
- **Description:** If `true`, the login form is hidden and only the OIDC, OAuth, LDAP, or WebAuthn providers are shown. This is useful if you want to enforce a specific authentication method. - **Description:** If `true`, the login form is hidden and only the OIDC, OAuth, LDAP, or WebAuthn providers are shown. This is useful if you want to enforce a specific authentication method.
If no social login providers are configured, the login form is always shown, regardless of this setting. If no social login providers are configured, the login form is always shown, regardless of this setting.
- **Important:** You can still access the login form by adding the `?all` query parameter to the login URL (e.g. https://wg.portal/#/login?all). - **Important:** You can still access the login form by adding the `?all` query parameter to the login URL (e.g. https://wg.portal/#/login?all).
@@ -715,6 +769,7 @@ The `webauthn` section contains configuration options for WebAuthn authenticatio
#### `enabled` #### `enabled`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_AUTH_WEBAUTHN_ENABLED`
- **Description:** If `true`, Passkey authentication is enabled. If `false`, WebAuthn is disabled. - **Description:** If `true`, Passkey authentication is enabled. If `false`, WebAuthn is disabled.
Users are encouraged to use Passkeys for secure authentication instead of passwords. Users are encouraged to use Passkeys for secure authentication instead of passwords.
If a passkey is registered, the password login is still available as a fallback. Ensure that the password is strong and secure. If a passkey is registered, the password login is still available as a fallback. Ensure that the password is strong and secure.
@@ -727,48 +782,59 @@ Without a valid `external_url`, the login process may fail due to CSRF protectio
### `listening_address` ### `listening_address`
- **Default:** `:8888` - **Default:** `:8888`
- **Environment Variable:** `WG_PORTAL_WEB_LISTENING_ADDRESS`
- **Description:** The listening address and port for the web server (e.g., `:8888` to bind on all interfaces or `127.0.0.1:8888` to bind only on the loopback interface). - **Description:** The listening address and port for the web server (e.g., `:8888` to bind on all interfaces or `127.0.0.1:8888` to bind only on the loopback interface).
Ensure that access to WireGuard Portal is protected against unauthorized access, especially if binding to all interfaces. Ensure that access to WireGuard Portal is protected against unauthorized access, especially if binding to all interfaces.
### `external_url` ### `external_url`
- **Default:** `http://localhost:8888` - **Default:** `http://localhost:8888`
- **Environment Variable:** `WG_PORTAL_WEB_EXTERNAL_URL`
- **Description:** The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects. - **Description:** The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects.
**Important:** If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server. **Important:** If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server.
### `site_company_name` ### `site_company_name`
- **Default:** `WireGuard Portal` - **Default:** `WireGuard Portal`
- **Environment Variable:** `WG_PORTAL_WEB_SITE_COMPANY_NAME`
- **Description:** The company name that is shown at the bottom of the web frontend. - **Description:** The company name that is shown at the bottom of the web frontend.
### `site_title` ### `site_title`
- **Default:** `WireGuard Portal` - **Default:** `WireGuard Portal`
- **Environment Variable:** `WG_PORTAL_WEB_SITE_TITLE`
- **Description:** The title that is shown in the web frontend. - **Description:** The title that is shown in the web frontend.
### `session_identifier` ### `session_identifier`
- **Default:** `wgPortalSession` - **Default:** `wgPortalSession`
- **Environment Variable:** `WG_PORTAL_WEB_SESSION_IDENTIFIER`
- **Description:** The session identifier for the web frontend. - **Description:** The session identifier for the web frontend.
### `session_secret` ### `session_secret`
- **Default:** `very_secret` - **Default:** `very_secret`
- **Environment Variable:** `WG_PORTAL_WEB_SESSION_SECRET`
- **Description:** The session secret for the web frontend. - **Description:** The session secret for the web frontend.
### `csrf_secret` ### `csrf_secret`
- **Default:** `extremely_secret` - **Default:** `extremely_secret`
- **Environment Variable:** `WG_PORTAL_WEB_CSRF_SECRET`
- **Description:** The CSRF secret. - **Description:** The CSRF secret.
### `request_logging` ### `request_logging`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_WEB_REQUEST_LOGGING`
- **Description:** Log all HTTP requests. - **Description:** Log all HTTP requests.
### `expose_host_info` ### `expose_host_info`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_WEB_EXPOSE_HOST_INFO`
- **Description:** Expose the hostname and version of the WireGuard Portal server in an HTTP header. This is useful for debugging but may expose sensitive information. - **Description:** Expose the hostname and version of the WireGuard Portal server in an HTTP header. This is useful for debugging but may expose sensitive information.
### `cert_file` ### `cert_file`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_WEB_CERT_FILE`
- **Description:** (Optional) Path to the TLS certificate file. - **Description:** (Optional) Path to the TLS certificate file.
### `key_file` ### `key_file`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_WEB_KEY_FILE`
- **Description:** (Optional) Path to the TLS certificate key file. - **Description:** (Optional) Path to the TLS certificate key file.
--- ---
@@ -780,12 +846,15 @@ Further details can be found in the [usage documentation](../usage/webhooks.md).
### `url` ### `url`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_WEBHOOK_URL`
- **Description:** The POST endpoint to which the webhook is sent. The URL must be reachable from the WireGuard Portal server. If the URL is empty, the webhook is disabled. - **Description:** The POST endpoint to which the webhook is sent. The URL must be reachable from the WireGuard Portal server. If the URL is empty, the webhook is disabled.
### `authentication` ### `authentication`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_WEBHOOK_AUTHENTICATION`
- **Description:** The Authorization header for the webhook endpoint. The value is send as-is in the header. For example: `Bearer <token>`. - **Description:** The Authorization header for the webhook endpoint. The value is send as-is in the header. For example: `Bearer <token>`.
### `timeout` ### `timeout`
- **Default:** `10s` - **Default:** `10s`
- **Description:** The timeout for the webhook request. If the request takes longer than this, it is aborted. - **Environment Variable:** `WG_PORTAL_WEBHOOK_TIMEOUT`
- **Description:** The timeout for the webhook request. If the request takes longer than this, it is aborted.

View File

@@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
"strconv"
"strings"
"time" "time"
"github.com/a8m/envsubst" "github.com/a8m/envsubst"
@@ -114,82 +116,93 @@ func (c *Config) LogStartupValues() {
func defaultConfig() *Config { func defaultConfig() *Config {
cfg := &Config{} cfg := &Config{}
cfg.Core.AdminUserDisabled = false cfg.Core.AdminUserDisabled = getEnvBool("WG_PORTAL_CORE_DISABLE_ADMIN_USER", false)
cfg.Core.AdminUser = "admin@wgportal.local" cfg.Core.AdminUser = getEnvStr("WG_PORTAL_CORE_ADMIN_USER", "admin@wgportal.local")
cfg.Core.AdminPassword = "wgportal-default" cfg.Core.AdminPassword = getEnvStr("WG_PORTAL_CORE_ADMIN_PASSWORD", "wgportal-default")
cfg.Core.AdminApiToken = "" // by default, the API access is disabled cfg.Core.AdminApiToken = getEnvStr("WG_PORTAL_CORE_ADMIN_API_TOKEN", "") // by default, the API access is disabled
cfg.Core.ImportExisting = true cfg.Core.ImportExisting = getEnvBool("WG_PORTAL_CORE_IMPORT_EXISTING", true)
cfg.Core.RestoreState = true cfg.Core.RestoreState = getEnvBool("WG_PORTAL_CORE_RESTORE_STATE", true)
cfg.Core.CreateDefaultPeer = false cfg.Core.CreateDefaultPeer = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER", false)
cfg.Core.CreateDefaultPeerOnCreation = false cfg.Core.CreateDefaultPeerOnCreation = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION", false)
cfg.Core.EditableKeys = true cfg.Core.EditableKeys = getEnvBool("WG_PORTAL_CORE_EDITABLE_KEYS", true)
cfg.Core.SelfProvisioningAllowed = false cfg.Core.SelfProvisioningAllowed = getEnvBool("WG_PORTAL_CORE_SELF_PROVISIONING_ALLOWED", false)
cfg.Core.ReEnablePeerAfterUserEnable = true cfg.Core.ReEnablePeerAfterUserEnable = getEnvBool("WG_PORTAL_CORE_RE_ENABLE_PEER_AFTER_USER_ENABLE", true)
cfg.Core.DeletePeerAfterUserDeleted = false cfg.Core.DeletePeerAfterUserDeleted = getEnvBool("WG_PORTAL_CORE_DELETE_PEER_AFTER_USER_DELETED", false)
cfg.Database = DatabaseConfig{ cfg.Database = DatabaseConfig{
Type: "sqlite", Debug: getEnvBool("WG_PORTAL_DATABASE_DEBUG", false),
DSN: "data/sqlite.db", SlowQueryThreshold: getEnvDuration("WG_PORTAL_DATABASE_SLOW_QUERY_THRESHOLD", 0),
Type: SupportedDatabase(getEnvStr("WG_PORTAL_DATABASE_TYPE", "sqlite")),
DSN: getEnvStr("WG_PORTAL_DATABASE_DSN", "data/sqlite.db"),
EncryptionPassphrase: getEnvStr("WG_PORTAL_DATABASE_ENCRYPTION_PASSPHRASE", ""),
} }
cfg.Backend = Backend{ cfg.Backend = Backend{
Default: LocalBackendName, // local backend is the default (using wgcrtl) Default: LocalBackendName, // local backend is the default (using wgcrtl)
IgnoredLocalInterfaces: getEnvStrSlice("WG_PORTAL_BACKEND_IGNORED_LOCAL_INTERFACES", nil),
// Most resolconf implementations use "tun." as a prefix for interface names. // Most resolconf implementations use "tun." as a prefix for interface names.
// But systemd's implementation uses no prefix, for example. // But systemd's implementation uses no prefix, for example.
LocalResolvconfPrefix: "tun.", LocalResolvconfPrefix: getEnvStr("WG_PORTAL_BACKEND_LOCAL_RESOLVCONF_PREFIX", "tun."),
} }
cfg.Web = WebConfig{ cfg.Web = WebConfig{
RequestLogging: false, RequestLogging: getEnvBool("WG_PORTAL_WEB_REQUEST_LOGGING", false),
ExternalUrl: "http://localhost:8888", ExposeHostInfo: getEnvBool("WG_PORTAL_WEB_EXPOSE_HOST_INFO", false),
ListeningAddress: ":8888", ExternalUrl: getEnvStr("WG_PORTAL_WEB_EXTERNAL_URL", "http://localhost:8888"),
SessionIdentifier: "wgPortalSession", ListeningAddress: getEnvStr("WG_PORTAL_WEB_LISTENING_ADDRESS", ":8888"),
SessionSecret: "very_secret", SessionIdentifier: getEnvStr("WG_PORTAL_WEB_SESSION_IDENTIFIER", "wgPortalSession"),
CsrfSecret: "extremely_secret", SessionSecret: getEnvStr("WG_PORTAL_WEB_SESSION_SECRET", "very_secret"),
SiteTitle: "WireGuard Portal", CsrfSecret: getEnvStr("WG_PORTAL_WEB_CSRF_SECRET", "extremely_secret"),
SiteCompanyName: "WireGuard Portal", SiteTitle: getEnvStr("WG_PORTAL_WEB_SITE_TITLE", "WireGuard Portal"),
SiteCompanyName: getEnvStr("WG_PORTAL_WEB_SITE_COMPANY_NAME", "WireGuard Portal"),
CertFile: getEnvStr("WG_PORTAL_WEB_CERT_FILE", ""),
KeyFile: getEnvStr("WG_PORTAL_WEB_KEY_FILE", ""),
} }
cfg.Advanced.LogLevel = "info" cfg.Advanced.LogLevel = getEnvStr("WG_PORTAL_ADVANCED_LOG_LEVEL", "info")
cfg.Advanced.StartListenPort = 51820 cfg.Advanced.LogPretty = getEnvBool("WG_PORTAL_ADVANCED_LOG_PRETTY", false)
cfg.Advanced.StartCidrV4 = "10.11.12.0/24" cfg.Advanced.LogJson = getEnvBool("WG_PORTAL_ADVANCED_LOG_JSON", false)
cfg.Advanced.StartCidrV6 = "fdfd:d3ad:c0de:1234::0/64" cfg.Advanced.StartListenPort = getEnvInt("WG_PORTAL_ADVANCED_START_LISTEN_PORT", 51820)
cfg.Advanced.UseIpV6 = true cfg.Advanced.StartCidrV4 = getEnvStr("WG_PORTAL_ADVANCED_START_CIDR_V4", "10.11.12.0/24")
cfg.Advanced.ExpiryCheckInterval = 15 * time.Minute cfg.Advanced.StartCidrV6 = getEnvStr("WG_PORTAL_ADVANCED_START_CIDR_V6", "fdfd:d3ad:c0de:1234::0/64")
cfg.Advanced.RulePrioOffset = 20000 cfg.Advanced.UseIpV6 = getEnvBool("WG_PORTAL_ADVANCED_USE_IP_V6", true)
cfg.Advanced.RouteTableOffset = 20000 cfg.Advanced.ConfigStoragePath = getEnvStr("WG_PORTAL_ADVANCED_CONFIG_STORAGE_PATH", "")
cfg.Advanced.ApiAdminOnly = true cfg.Advanced.ExpiryCheckInterval = getEnvDuration("WG_PORTAL_ADVANCED_EXPIRY_CHECK_INTERVAL", 15*time.Minute)
cfg.Advanced.LimitAdditionalUserPeers = 0 cfg.Advanced.RulePrioOffset = getEnvInt("WG_PORTAL_ADVANCED_RULE_PRIO_OFFSET", 20000)
cfg.Advanced.RouteTableOffset = getEnvInt("WG_PORTAL_ADVANCED_ROUTE_TABLE_OFFSET", 20000)
cfg.Advanced.ApiAdminOnly = getEnvBool("WG_PORTAL_ADVANCED_API_ADMIN_ONLY", true)
cfg.Advanced.LimitAdditionalUserPeers = getEnvInt("WG_PORTAL_ADVANCED_LIMIT_ADDITIONAL_USER_PEERS", 0)
cfg.Statistics.UsePingChecks = true cfg.Statistics.UsePingChecks = getEnvBool("WG_PORTAL_STATISTICS_USE_PING_CHECKS", true)
cfg.Statistics.PingCheckWorkers = 10 cfg.Statistics.PingCheckWorkers = getEnvInt("WG_PORTAL_STATISTICS_PING_CHECK_WORKERS", 10)
cfg.Statistics.PingUnprivileged = false cfg.Statistics.PingUnprivileged = getEnvBool("WG_PORTAL_STATISTICS_PING_UNPRIVILEGED", false)
cfg.Statistics.PingCheckInterval = 1 * time.Minute cfg.Statistics.PingCheckInterval = getEnvDuration("WG_PORTAL_STATISTICS_PING_CHECK_INTERVAL", 1*time.Minute)
cfg.Statistics.DataCollectionInterval = 1 * time.Minute cfg.Statistics.DataCollectionInterval = getEnvDuration("WG_PORTAL_STATISTICS_DATA_COLLECTION_INTERVAL", 1*time.Minute)
cfg.Statistics.CollectInterfaceData = true cfg.Statistics.CollectInterfaceData = getEnvBool("WG_PORTAL_STATISTICS_COLLECT_INTERFACE_DATA", true)
cfg.Statistics.CollectPeerData = true cfg.Statistics.CollectPeerData = getEnvBool("WG_PORTAL_STATISTICS_COLLECT_PEER_DATA", true)
cfg.Statistics.CollectAuditData = true cfg.Statistics.CollectAuditData = getEnvBool("WG_PORTAL_STATISTICS_COLLECT_AUDIT_DATA", true)
cfg.Statistics.ListeningAddress = ":8787" cfg.Statistics.ListeningAddress = getEnvStr("WG_PORTAL_STATISTICS_LISTENING_ADDRESS", ":8787")
cfg.Mail = MailConfig{ cfg.Mail = MailConfig{
Host: "127.0.0.1", Host: getEnvStr("WG_PORTAL_MAIL_HOST", "127.0.0.1"),
Port: 25, Port: getEnvInt("WG_PORTAL_MAIL_PORT", 25),
Encryption: MailEncryptionNone, Encryption: MailEncryption(getEnvStr("WG_PORTAL_MAIL_ENCRYPTION", string(MailEncryptionNone))),
CertValidation: true, CertValidation: getEnvBool("WG_PORTAL_MAIL_CERT_VALIDATION", true),
Username: "", Username: getEnvStr("WG_PORTAL_MAIL_USERNAME", ""),
Password: "", Password: getEnvStr("WG_PORTAL_MAIL_PASSWORD", ""),
AuthType: MailAuthPlain, AuthType: MailAuthType(getEnvStr("WG_PORTAL_MAIL_AUTH_TYPE", string(MailAuthPlain))),
From: "Wireguard Portal <noreply@wireguard.local>", From: getEnvStr("WG_PORTAL_MAIL_FROM", "Wireguard Portal <noreply@wireguard.local>"),
LinkOnly: false, LinkOnly: getEnvBool("WG_PORTAL_MAIL_LINK_ONLY", false),
AllowPeerEmail: getEnvBool("WG_PORTAL_MAIL_ALLOW_PEER_EMAIL", false),
} }
cfg.Webhook.Url = "" // no webhook by default cfg.Webhook.Url = getEnvStr("WG_PORTAL_WEBHOOK_URL", "") // no webhook by default
cfg.Webhook.Authentication = "" cfg.Webhook.Authentication = getEnvStr("WG_PORTAL_WEBHOOK_AUTHENTICATION", "")
cfg.Webhook.Timeout = 10 * time.Second cfg.Webhook.Timeout = getEnvDuration("WG_PORTAL_WEBHOOK_TIMEOUT", 10*time.Second)
cfg.Auth.WebAuthn.Enabled = true cfg.Auth.WebAuthn.Enabled = getEnvBool("WG_PORTAL_AUTH_WEBAUTHN_ENABLED", true)
cfg.Auth.MinPasswordLength = 16 cfg.Auth.MinPasswordLength = getEnvInt("WG_PORTAL_AUTH_MIN_PASSWORD_LENGTH", 16)
cfg.Auth.HideLoginForm = false cfg.Auth.HideLoginForm = getEnvBool("WG_PORTAL_AUTH_HIDE_LOGIN_FORM", false)
return cfg return cfg
} }
@@ -244,3 +257,75 @@ func loadConfigFile(cfg any, filename string) error {
return nil return nil
} }
func getEnvStr(name, fallback string) string {
if v, ok := os.LookupEnv(name); ok {
return v
}
return fallback
}
func getEnvStrSlice(name string, fallback []string) []string {
v, ok := os.LookupEnv(name)
if !ok {
return fallback
}
strParts := strings.Split(v, ",")
stringSlice := make([]string, 0, len(strParts))
for _, s := range strParts {
trimmed := strings.TrimSpace(s)
if trimmed != "" {
stringSlice = append(stringSlice, trimmed)
}
}
return stringSlice
}
func getEnvBool(name string, fallback bool) bool {
v, ok := os.LookupEnv(name)
if !ok {
return fallback
}
b, err := strconv.ParseBool(v)
if err != nil {
slog.Warn("invalid bool env, using fallback", "env", name, "value", v, "fallback", fallback)
return fallback
}
return b
}
func getEnvInt(name string, fallback int) int {
v, ok := os.LookupEnv(name)
if !ok {
return fallback
}
i, err := strconv.Atoi(v)
if err != nil {
slog.Warn("invalid int env, using fallback", "env", name, "value", v, "fallback", fallback)
return fallback
}
return i
}
func getEnvDuration(name string, fallback time.Duration) time.Duration {
v, ok := os.LookupEnv(name)
if !ok {
return fallback
}
d, err := time.ParseDuration(v)
if err != nil {
slog.Warn("invalid duration env, using fallback", "env", name, "value", v, "fallback", fallback)
return fallback
}
return d
}