Browse Source

Merge da61cb680f into c70ad1d08b

pull/2283/merge
Copilot 1 day ago
committed by GitHub
parent
commit
d7b0e07738
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 71
      docs/content/advanced/config/optional-config.md
  2. 16
      docs/content/advanced/config/unattended-setup.md
  3. 4
      docs/content/advanced/migrate/from-14-to-15.md
  4. 13
      src/app/components/Form/ArrayField.vue
  5. 7
      src/app/components/Form/HostField.vue
  6. 7
      src/app/components/Form/NullTextField.vue
  7. 13
      src/app/components/Form/NumberField.vue
  8. 13
      src/app/components/Form/SwitchField.vue
  9. 7
      src/app/components/Form/TextField.vue
  10. 17
      src/app/pages/admin/config.vue
  11. 11
      src/app/pages/admin/general.vue
  12. 10
      src/app/pages/admin/hooks.vue
  13. 9
      src/app/pages/admin/interface.vue
  14. 10
      src/app/pages/setup/2.vue
  15. 1
      src/server/api/admin/interface/cidr.post.ts
  16. 34
      src/server/api/admin/overrides.get.ts
  17. 17
      src/server/api/setup/2.post.ts
  18. 21
      src/server/database/repositories/client/service.ts
  19. 26
      src/server/database/sqlite.ts
  20. 7
      src/server/middleware/setup.ts
  21. 47
      src/server/utils/WireGuard.ts
  22. 174
      src/server/utils/config.ts
  23. 4
      src/server/utils/handler.ts
  24. 10
      src/server/utils/session.ts

71
docs/content/advanced/config/optional-config.md

@ -21,3 +21,74 @@ 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.
///
## 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.
///

16
docs/content/advanced/config/unattended-setup.md

@ -11,18 +11,20 @@ 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_USERNAME` | `admin` | Sets admin username | 1 |
| `INIT_PASSWORD` | `Se!ureP%ssw` | Sets admin password | 1 |
| `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 1 |
| `INIT_PORT` | `51820` | Port clients will connect to and wireguard will listen on | 1 |
| `INIT_DNS` | `1.1.1.1,8.8.8.8` | Sets global dns setting | 2 |
| `INIT_IPV4_CIDR` | `10.8.0.0/24` | Sets IPv4 cidr | 3 |
| `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 3 |
| `INIT_ALLOWED_IPS` | `10.8.0.0/24,2001:0DB8::/32` | Sets global Allowed IPs | 4 |
| `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 2 |
| `INIT_PORT` | `51820` | Port clients will connect to and WireGuard will listen on | 2 |
| `INIT_DNS` | `1.1.1.1,8.8.8.8` | Sets global dns setting | 3 |
| `INIT_IPV4_CIDR` | `10.8.0.0/24` | Sets IPv4 cidr | 4 |
| `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 4 |
| `INIT_ALLOWED_IPS` | `10.8.0.0/24,2001:0DB8::/32` | Sets global Allowed IPs | 5 |
/// warning | Variables have to be used together
If variables are in the same group, you have to set all of them. For example, if you set `INIT_IPV4_CIDR`, you also have to set `INIT_IPV6_CIDR`.
If you want to skip the setup process, you have to configure group `1`
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.
Avoid setting both `INIT_` and `WG_` variables for the same setting to prevent confusion.
///
/// note | Security

4
docs/content/advanced/migrate/from-14-to-15.md

@ -51,7 +51,9 @@ In the setup wizard, select that you already have a configuration file and uploa
### Environment Variables
v15 does not use the same environment variables as v14, most of them have been moved to the Admin Panel in the Web UI.
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.
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

13
src/app/components/Form/ArrayField.vue

@ -1,5 +1,12 @@
<template>
<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">
{{ emptyText || $t('form.noItems') }}
</div>
@ -27,7 +34,11 @@
<script lang="ts" setup>
const data = defineModel<string[]>();
defineProps<{ emptyText?: string[]; name: string }>();
defineProps<{
emptyText?: string[];
name: string;
overridden?: boolean;
}>();
function update(e: Event, i: number) {
const v = (e.target as HTMLInputElement).value;

7
src/app/components/Form/HostField.vue

@ -6,6 +6,12 @@
<BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" />
</BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div>
<div class="flex gap-1">
<BaseInput
@ -38,6 +44,7 @@ defineProps<{
description?: string;
placeholder?: string;
url: '/api/admin/ip-info' | '/api/setup/4';
overridden?: boolean;
}>();
const data = defineModel<string | null>({

7
src/app/components/Form/NullTextField.vue

@ -6,6 +6,12 @@
<BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" />
</BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div>
<BaseInput
:id="id"
@ -24,6 +30,7 @@ defineProps<{
description?: string;
autocomplete?: string;
placeholder?: string;
overridden?: boolean;
}>();
const data = defineModel<string | null>({

13
src/app/components/Form/NumberField.vue

@ -6,12 +6,23 @@
<BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" />
</BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div>
<BaseInput :id="id" v-model.number="data" :name="id" type="number" />
</template>
<script lang="ts" setup>
defineProps<{ id: string; label: string; description?: string }>();
defineProps<{
id: string;
label: string;
description?: string;
overridden?: boolean;
}>();
const data = defineModel<number>();
</script>

13
src/app/components/Form/SwitchField.vue

@ -6,6 +6,12 @@
<BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" />
</BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div>
<div class="my-auto">
<BaseSwitch :id="id" v-model="data" />
@ -13,6 +19,11 @@
</template>
<script lang="ts" setup>
defineProps<{ id: string; label: string; description?: string }>();
defineProps<{
id: string;
label: string;
description?: string;
overridden?: boolean;
}>();
const data = defineModel<boolean>();
</script>

7
src/app/components/Form/TextField.vue

@ -6,6 +6,12 @@
<BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" />
</BaseTooltip>
<BaseTooltip
v-if="overridden"
text="This field is overridden by an environment variable"
>
<IconsWarning class="size-4 text-amber-500" />
</BaseTooltip>
</div>
<BaseInput
:id="id"
@ -24,6 +30,7 @@ defineProps<{
description?: string;
autocomplete?: string;
disabled?: boolean;
overridden?: boolean;
}>();
const data = defineModel<string>();

17
src/app/pages/admin/config.vue

@ -9,12 +9,14 @@
:label="$t('general.host')"
:description="$t('admin.config.hostDesc')"
url="/api/admin/ip-info"
:overridden="overrides?.host"
/>
<FormNumberField
id="port"
v-model="data.port"
:label="$t('general.port')"
:description="$t('admin.config.portDesc')"
:overridden="overrides?.port"
/>
</FormGroup>
<FormGroup>
@ -24,13 +26,18 @@
<FormArrayField
v-model="data.defaultAllowedIps"
name="defaultAllowedIps"
:overridden="overrides?.defaultAllowedIps"
/>
</FormGroup>
<FormGroup>
<FormHeading :description="$t('admin.config.dnsDesc')">
{{ $t('general.dns') }}
</FormHeading>
<FormArrayField v-model="data.defaultDns" name="defaultDns" />
<FormArrayField
v-model="data.defaultDns"
name="defaultDns"
:overridden="overrides?.defaultDns"
/>
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading>
@ -39,12 +46,14 @@
v-model="data.defaultMtu"
:label="$t('general.mtu')"
:description="$t('admin.config.mtuDesc')"
:overridden="overrides?.defaultMtu"
/>
<FormNumberField
id="defaultPersistentKeepalive"
v-model="data.defaultPersistentKeepalive"
:label="$t('general.persistentKeepalive')"
:description="$t('admin.config.persistentKeepaliveDesc')"
:overridden="overrides?.defaultPersistentKeepalive"
/>
</FormGroup>
<FormGroup v-if="globalStore.information?.isAwg">
@ -118,6 +127,12 @@ const { data: _data, refresh } = await useFetch(`/api/admin/userconfig`, {
method: 'get',
});
const { data: overridesData } = await useFetch(`/api/admin/overrides`, {
method: 'get',
});
const overrides = computed(() => overridesData.value?.userConfig);
const data = toRef(_data.value);
const _submit = useSubmit(

11
src/app/pages/admin/general.vue

@ -7,6 +7,7 @@
v-model="data.sessionTimeout"
:label="$t('admin.general.sessionTimeout')"
:description="$t('admin.general.sessionTimeoutDesc')"
:overridden="overrides?.sessionTimeout"
/>
</FormGroup>
<FormGroup>
@ -16,18 +17,21 @@
v-model="data.metricsPassword"
:label="$t('admin.general.metricsPassword')"
:description="$t('admin.general.metricsPasswordDesc')"
:overridden="overrides?.metricsPassword"
/>
<FormSwitchField
id="prometheus"
v-model="data.metricsPrometheus"
:label="$t('admin.general.prometheus')"
:description="$t('admin.general.prometheusDesc')"
:overridden="overrides?.metricsPrometheus"
/>
<FormSwitchField
id="json"
v-model="data.metricsJson"
:label="$t('admin.general.json')"
:description="$t('admin.general.jsonDesc')"
:overridden="overrides?.metricsJson"
/>
</FormGroup>
<FormGroup>
@ -43,6 +47,13 @@
const { data: _data, refresh } = await useFetch(`/api/admin/general`, {
method: 'get',
});
const { data: overridesData } = await useFetch(`/api/admin/overrides`, {
method: 'get',
});
const overrides = computed(() => overridesData.value?.general);
const data = toRef(_data.value);
const _submit = useSubmit(

10
src/app/pages/admin/hooks.vue

@ -6,21 +6,25 @@
id="PreUp"
v-model="data.preUp"
:label="$t('hooks.preUp')"
:overridden="overrides?.preUp"
/>
<FormTextArea
id="PostUp"
v-model="data.postUp"
:label="$t('hooks.postUp')"
:overridden="overrides?.postUp"
/>
<FormTextArea
id="PreDown"
v-model="data.preDown"
:label="$t('hooks.preDown')"
:overridden="overrides?.preDown"
/>
<FormTextArea
id="PostDown"
v-model="data.postDown"
:label="$t('hooks.postDown')"
:overridden="overrides?.postDown"
/>
</FormGroup>
<FormGroup>
@ -37,6 +41,12 @@ const { data: _data, refresh } = await useFetch(`/api/admin/hooks`, {
method: 'get',
});
const { data: overridesData } = await useFetch(`/api/admin/overrides`, {
method: 'get',
});
const overrides = computed(() => overridesData.value?.hooks);
const data = toRef(_data.value);
const _submit = useSubmit(

9
src/app/pages/admin/interface.vue

@ -7,18 +7,21 @@
v-model="data.mtu"
:label="$t('general.mtu')"
:description="$t('admin.interface.mtuDesc')"
:overridden="overrides?.mtu"
/>
<FormNumberField
id="port"
v-model="data.port"
:label="$t('general.port')"
:description="$t('admin.interface.portDesc')"
:overridden="overrides?.port"
/>
<FormTextField
id="device"
v-model="data.device"
:label="$t('admin.interface.device')"
:description="$t('admin.interface.deviceDesc')"
:overridden="overrides?.device"
/>
</FormGroup>
<FormGroup v-if="globalStore.information?.isAwg">
@ -173,6 +176,12 @@ const { data: _data, refresh } = await useFetch(`/api/admin/interface`, {
method: 'get',
});
const { data: overridesData } = await useFetch(`/api/admin/overrides`, {
method: 'get',
});
const overrides = computed(() => overridesData.value?.interface);
const data = toRef(_data.value);
const _submit = useSubmit(

10
src/app/pages/setup/2.vue

@ -56,9 +56,15 @@ const _submit = useSubmit(
body: data,
}),
{
revert: async (success) => {
revert: async (success, data) => {
if (success) {
await navigateTo('/setup/3');
if (data?.setupDone) {
// Setup is complete, redirect to success page
await navigateTo('/setup/success');
} else {
// Continue to step 3
await navigateTo('/setup/3');
}
}
},
noSuccessToast: true,

1
src/server/api/admin/interface/cidr.post.ts

@ -8,7 +8,6 @@ export default definePermissionEventHandler(
event,
validateZod(InterfaceCidrUpdateSchema, event)
);
await Database.interfaces.updateCidr(data);
await WireGuard.saveConfig();
return { success: true };

34
src/server/api/admin/overrides.get.ts

@ -0,0 +1,34 @@
export default definePermissionEventHandler('admin', 'any', async () => {
return {
interface: {
port: WG_OVERRIDE_ENV.PORT !== undefined,
device: WG_OVERRIDE_ENV.DEVICE !== undefined,
mtu: WG_OVERRIDE_ENV.MTU !== undefined,
ipv4Cidr: WG_OVERRIDE_ENV.IPV4_CIDR !== undefined,
ipv6Cidr: WG_OVERRIDE_ENV.IPV6_CIDR !== undefined,
},
userConfig: {
host: WG_CLIENT_OVERRIDE_ENV.HOST !== undefined,
port: WG_CLIENT_OVERRIDE_ENV.CLIENT_PORT !== undefined,
defaultDns: WG_CLIENT_OVERRIDE_ENV.DEFAULT_DNS !== undefined,
defaultAllowedIps:
WG_CLIENT_OVERRIDE_ENV.DEFAULT_ALLOWED_IPS !== undefined,
defaultMtu: WG_CLIENT_OVERRIDE_ENV.DEFAULT_MTU !== undefined,
defaultPersistentKeepalive:
WG_CLIENT_OVERRIDE_ENV.DEFAULT_PERSISTENT_KEEPALIVE !== undefined,
},
general: {
sessionTimeout: WG_GENERAL_OVERRIDE_ENV.SESSION_TIMEOUT !== undefined,
metricsPassword: WG_GENERAL_OVERRIDE_ENV.METRICS_PASSWORD !== undefined,
metricsPrometheus:
WG_GENERAL_OVERRIDE_ENV.METRICS_PROMETHEUS !== undefined,
metricsJson: WG_GENERAL_OVERRIDE_ENV.METRICS_JSON !== undefined,
},
hooks: {
preUp: WG_HOOKS_OVERRIDE_ENV.PRE_UP !== undefined,
postUp: WG_HOOKS_OVERRIDE_ENV.POST_UP !== undefined,
preDown: WG_HOOKS_OVERRIDE_ENV.PRE_DOWN !== undefined,
postDown: WG_HOOKS_OVERRIDE_ENV.POST_DOWN !== undefined,
},
};
});

17
src/server/api/setup/2.post.ts

@ -8,6 +8,19 @@ export default defineSetupEventHandler(2, async ({ event }) => {
await Database.users.create(username, password);
await Database.general.setSetupStep(3);
return { success: true };
// If host and port are already set by environment variables, skip step 4
const host = WG_INITIAL_ENV.HOST ?? WG_CLIENT_OVERRIDE_ENV.HOST;
const port = WG_INITIAL_ENV.PORT ?? WG_INTERFACE_OVERRIDE_ENV.PORT;
const setupDone = host && port;
if (setupDone) {
// Skip to done
await Database.general.setSetupStep(0);
} else {
// Proceed to step 3 (which leads to step 4)
await Database.general.setSetupStep(3);
}
return { success: true, setupDone: setupDone };
});

21
src/server/database/repositories/client/service.ts

@ -175,26 +175,30 @@ export class ClientService {
return this.#db.transaction(async (tx) => {
const clients = await tx.query.client.findMany().execute();
const clientInterface = await tx.query.wgInterface
const _clientInterface = await tx.query.wgInterface
.findFirst({
where: eq(wgInterface.name, 'wg0'),
})
.execute();
if (!clientInterface) {
if (!_clientInterface) {
throw new Error('WireGuard interface not found');
}
const clientConfig = await tx.query.userConfig
const clientInterface = applyInterfaceOverrides(_clientInterface);
const _clientConfig = await tx.query.userConfig
.findFirst({
where: eq(userConfig.id, clientInterface.name),
})
.execute();
if (!clientConfig) {
if (!_clientConfig) {
throw new Error('WireGuard interface configuration not found');
}
const clientConfig = applyUserConfigOverrides(_clientConfig);
const ipv4Cidr = parseCidr(clientInterface.ipv4Cidr);
const ipv4Address = nextIP(4, ipv4Cidr, clients);
const ipv6Cidr = parseCidr(clientInterface.ipv6Cidr);
@ -241,16 +245,18 @@ export class ClientService {
update(id: ID, data: UpdateClientType) {
return this.#db.transaction(async (tx) => {
const clientInterface = await tx.query.wgInterface
const _clientInterface = await tx.query.wgInterface
.findFirst({
where: eq(wgInterface.name, 'wg0'),
})
.execute();
if (!clientInterface) {
if (!_clientInterface) {
throw new Error('WireGuard interface not found');
}
const clientInterface = applyInterfaceOverrides(_clientInterface);
if (!containsCidr(clientInterface.ipv4Cidr, data.ipv4Address)) {
throw new Error('IPv4 address is not within the CIDR range');
}
@ -272,7 +278,8 @@ export class ClientService {
privateKey,
publicKey,
}: ClientCreateFromExistingType) {
const clientConfig = await Database.userConfigs.get();
const _clientConfig = await Database.userConfigs.get();
const clientConfig = applyUserConfigOverrides(_clientConfig);
return this.#db
.insert(client)

26
src/server/database/sqlite.ts

@ -101,22 +101,26 @@ async function initialSetup(db: DBServiceType) {
});
}
if (
WG_INITIAL_ENV.USERNAME &&
WG_INITIAL_ENV.PASSWORD &&
WG_INITIAL_ENV.HOST &&
WG_INITIAL_ENV.PORT
) {
if (WG_INITIAL_ENV.USERNAME && WG_INITIAL_ENV.PASSWORD) {
DB_DEBUG('Creating initial user...');
await db.users.create(WG_INITIAL_ENV.USERNAME, WG_INITIAL_ENV.PASSWORD);
await db.general.setSetupStep(3);
}
// Use INIT vars or fall back to override vars for HOST and PORT
const host = WG_INITIAL_ENV.HOST ?? WG_CLIENT_OVERRIDE_ENV.HOST;
const port = WG_INITIAL_ENV.PORT ?? WG_INTERFACE_OVERRIDE_ENV.PORT;
// HOST and PORT can come from either INIT vars or override vars
if (host && port) {
DB_DEBUG('Setting initial host and port...');
await db.userConfigs.updateHostPort(
WG_INITIAL_ENV.HOST,
WG_INITIAL_ENV.PORT
);
await db.userConfigs.updateHostPort(host, port);
await db.general.setSetupStep(0);
// Setup completion requires USERNAME and PASSWORD (no overrides for these)
if (WG_INITIAL_ENV.USERNAME && WG_INITIAL_ENV.PASSWORD) {
await db.general.setSetupStep(0);
}
}
}

7
src/server/middleware/setup.ts

@ -9,12 +9,17 @@ export default defineEventHandler(async (event) => {
const { step, done } = await Database.general.getSetupStep();
if (!done) {
const parsedSetup = url.pathname.match(/\/setup\/(\d)/);
const parsedSetup = url.pathname.match(/\/setup\/(\d|migrate|success)/);
if (!parsedSetup) {
return sendRedirect(event, `/setup/1`, 302);
}
const [_, currentSetup] = parsedSetup;
// Allow access to success page during setup
if (currentSetup === 'success') {
return;
}
if (step.toString() === currentSetup) {
return;
}

47
src/server/utils/WireGuard.ts

@ -12,7 +12,10 @@ class WireGuard {
* Save and sync config
*/
async saveConfig() {
const wgInterface = await Database.interfaces.get();
const wgInterface = applyInterfaceOverrides(
await Database.interfaces.get()
);
await this.#saveWireguardConfig(wgInterface);
await this.#syncWireguardConfig(wgInterface);
await this.#applyFirewallRules(wgInterface);
@ -39,7 +42,7 @@ class WireGuard {
*/
async #saveWireguardConfig(wgInterface: InterfaceType) {
const clients = await Database.clients.getAll();
const hooks = await Database.hooks.get();
const hooks = applyHooksOverrides(await Database.hooks.get());
const result = [];
result.push(
@ -164,8 +167,12 @@ class WireGuard {
}
async getClientConfiguration({ clientId }: { clientId: ID }) {
const wgInterface = await Database.interfaces.get();
const userConfig = await Database.userConfigs.get();
const wgInterface = applyInterfaceOverrides(
await Database.interfaces.get()
);
const userConfig = applyUserConfigOverrides(
await Database.userConfigs.get()
);
const client = await Database.clients.get(clientId);
@ -227,25 +234,33 @@ class WireGuard {
Database.interfaces.update(wgInterface);
}
WG_DEBUG(`Starting Wireguard Interface ${wgInterface.name}...`);
await this.#saveWireguardConfig(wgInterface);
await wg.down(wgInterface.name).catch(() => {});
await wg.up(wgInterface.name).catch((err) => {
const wgInterfaceWithOverrides = applyInterfaceOverrides(wgInterface);
WG_DEBUG(
`Starting Wireguard Interface ${wgInterfaceWithOverrides.name}...`
);
await this.#saveWireguardConfig(wgInterfaceWithOverrides);
await wg.down(wgInterfaceWithOverrides.name).catch(() => {});
await wg.up(wgInterfaceWithOverrides.name).catch((err) => {
if (
err &&
err.message &&
err.message.includes(`Cannot find device "${wgInterface.name}"`)
err.message.includes(
`Cannot find device "${wgInterfaceWithOverrides.name}"`
)
) {
throw new Error(
`WireGuard exited with the error: Cannot find device "${wgInterface.name}"\nThis usually means that your host's kernel does not support WireGuard!`,
`WireGuard exited with the error: Cannot find device "${wgInterfaceWithOverrides.name}"\nThis usually means that your host's kernel does not support WireGuard!`,
{ cause: err.message }
);
}
throw err;
});
await this.#syncWireguardConfig(wgInterface);
WG_DEBUG(`Wireguard Interface ${wgInterface.name} started successfully.`);
await this.#syncWireguardConfig(wgInterfaceWithOverrides);
WG_DEBUG(
`Wireguard Interface ${wgInterfaceWithOverrides.name} started successfully.`
);
// Check if firewall was enabled but iptables isn't available
if (wgInterface.firewallEnabled) {
@ -282,12 +297,16 @@ class WireGuard {
// Shutdown wireguard
async Shutdown() {
const wgInterface = await Database.interfaces.get();
const wgInterface = applyInterfaceOverrides(
await Database.interfaces.get()
);
await wg.down(wgInterface.name).catch(() => {});
}
async Restart() {
const wgInterface = await Database.interfaces.get();
const wgInterface = applyInterfaceOverrides(
await Database.interfaces.get()
);
await wg.restart(wgInterface.name);
}

174
src/server/utils/config.ts

@ -55,6 +55,78 @@ export const WG_INITIAL_ENV = {
: undefined,
};
export const WG_INTERFACE_OVERRIDE_ENV = {
/** Override the WireGuard interface port */
PORT: process.env.WG_PORT
? Number.parseInt(process.env.WG_PORT, 10)
: undefined,
/** Override the network device/interface */
DEVICE: process.env.WG_DEVICE,
/** Override the MTU setting */
MTU: process.env.WG_MTU ? Number.parseInt(process.env.WG_MTU, 10) : undefined,
/** Override the IPv4 CIDR */
IPV4_CIDR: process.env.WG_IPV4_CIDR,
/** Override the IPv6 CIDR */
IPV6_CIDR: process.env.WG_IPV6_CIDR,
};
export const WG_CLIENT_OVERRIDE_ENV = {
/** Override the client connection host */
HOST: process.env.WG_HOST,
/** Override the client connection port (falls back to Interface Port) */
CLIENT_PORT: process.env.WG_CLIENT_PORT
? Number.parseInt(process.env.WG_CLIENT_PORT, 10)
: WG_INTERFACE_OVERRIDE_ENV.PORT,
/** Override default client DNS servers */
DEFAULT_DNS: process.env.WG_DEFAULT_DNS?.split(',').map((x) => x.trim()),
/** Override default client allowed IPs */
DEFAULT_ALLOWED_IPS: process.env.WG_DEFAULT_ALLOWED_IPS?.split(',').map((x) =>
x.trim()
),
/** Override default client MTU */
DEFAULT_MTU: process.env.WG_DEFAULT_MTU
? Number.parseInt(process.env.WG_DEFAULT_MTU, 10)
: undefined,
/** Override default client persistent keepalive */
DEFAULT_PERSISTENT_KEEPALIVE: process.env.WG_DEFAULT_PERSISTENT_KEEPALIVE
? Number.parseInt(process.env.WG_DEFAULT_PERSISTENT_KEEPALIVE, 10)
: undefined,
};
export const WG_GENERAL_OVERRIDE_ENV = {
/** Override session timeout */
SESSION_TIMEOUT: process.env.WG_SESSION_TIMEOUT
? Number.parseInt(process.env.WG_SESSION_TIMEOUT, 10)
: undefined,
/** Override metrics password */
METRICS_PASSWORD: process.env.WG_METRICS_PASSWORD,
/** Override metrics Prometheus enabled status */
METRICS_PROMETHEUS:
process.env.WG_METRICS_PROMETHEUS === 'true'
? true
: process.env.WG_METRICS_PROMETHEUS === 'false'
? false
: undefined,
/** Override metrics JSON enabled status */
METRICS_JSON:
process.env.WG_METRICS_JSON === 'true'
? true
: process.env.WG_METRICS_JSON === 'false'
? false
: undefined,
};
export const WG_HOOKS_OVERRIDE_ENV = {
/** Override PreUp hook */
PRE_UP: process.env.WG_PRE_UP,
/** Override PostUp hook */
POST_UP: process.env.WG_POST_UP,
/** Override PreDown hook */
PRE_DOWN: process.env.WG_PRE_DOWN,
/** Override PostDown hook */
POST_DOWN: process.env.WG_POST_DOWN,
};
function assertEnv<T extends string>(env: T) {
const val = process.env[env];
@ -64,3 +136,105 @@ function assertEnv<T extends string>(env: T) {
return val;
}
/**
* Apply environment variable overrides to an interface object
*/
export function applyInterfaceOverrides<
T extends {
port: number;
device: string;
mtu: number;
ipv4Cidr: string;
ipv6Cidr: string;
},
>(wgInterface: T): T {
return {
...wgInterface,
port: WG_INTERFACE_OVERRIDE_ENV.PORT ?? wgInterface.port,
device: WG_INTERFACE_OVERRIDE_ENV.DEVICE ?? wgInterface.device,
mtu: WG_INTERFACE_OVERRIDE_ENV.MTU ?? wgInterface.mtu,
ipv4Cidr: WG_INTERFACE_OVERRIDE_ENV.IPV4_CIDR ?? wgInterface.ipv4Cidr,
ipv6Cidr: WG_INTERFACE_OVERRIDE_ENV.IPV6_CIDR ?? wgInterface.ipv6Cidr,
};
}
/**
* Apply environment variable overrides to a user config object
*/
export function applyUserConfigOverrides<
T extends {
host: string;
port: number;
defaultDns: string[];
defaultAllowedIps: string[];
defaultMtu: number;
defaultPersistentKeepalive: number;
},
>(userConfig: T): T {
return {
...userConfig,
host: WG_CLIENT_OVERRIDE_ENV.HOST ?? userConfig.host,
port: WG_CLIENT_OVERRIDE_ENV.CLIENT_PORT ?? userConfig.port,
defaultDns: WG_CLIENT_OVERRIDE_ENV.DEFAULT_DNS ?? userConfig.defaultDns,
defaultAllowedIps:
WG_CLIENT_OVERRIDE_ENV.DEFAULT_ALLOWED_IPS ??
userConfig.defaultAllowedIps,
defaultMtu: WG_CLIENT_OVERRIDE_ENV.DEFAULT_MTU ?? userConfig.defaultMtu,
defaultPersistentKeepalive:
WG_CLIENT_OVERRIDE_ENV.DEFAULT_PERSISTENT_KEEPALIVE ??
userConfig.defaultPersistentKeepalive,
};
}
/**
* Apply environment variable overrides to a general config object
*/
export function applySessionOverrides<
T extends {
sessionTimeout: number;
},
>(generalConfig: T): T {
return {
...generalConfig,
sessionTimeout:
WG_GENERAL_OVERRIDE_ENV.SESSION_TIMEOUT ?? generalConfig.sessionTimeout,
};
}
export function applyMetricsOverrides<
T extends {
password: string | null;
prometheus: boolean;
json: boolean;
},
>(metricsConfig: T): T {
return {
...metricsConfig,
password:
WG_GENERAL_OVERRIDE_ENV.METRICS_PASSWORD ?? metricsConfig.password,
prometheus:
WG_GENERAL_OVERRIDE_ENV.METRICS_PROMETHEUS ?? metricsConfig.prometheus,
json: WG_GENERAL_OVERRIDE_ENV.METRICS_JSON ?? metricsConfig.json,
};
}
/**
* Apply environment variable overrides to a hooks object
*/
export function applyHooksOverrides<
T extends {
preUp: string;
postUp: string;
preDown: string;
postDown: string;
},
>(hooks: T): T {
return {
...hooks,
preUp: WG_HOOKS_OVERRIDE_ENV.PRE_UP ?? hooks.preUp,
postUp: WG_HOOKS_OVERRIDE_ENV.POST_UP ?? hooks.postUp,
preDown: WG_HOOKS_OVERRIDE_ENV.PRE_DOWN ?? hooks.preDown,
postDown: WG_HOOKS_OVERRIDE_ENV.POST_DOWN ?? hooks.postDown,
};
}

4
src/server/utils/handler.ts

@ -138,7 +138,9 @@ export const defineMetricsHandler = <
handler: MetricsHandler<TReq, TRes>
) => {
return defineEventHandler(async (event) => {
const metricsConfig = await Database.general.getMetricsConfig();
const metricsConfig = applyMetricsOverrides(
await Database.general.getMetricsConfig()
);
if (metricsConfig.password) {
const auth = getHeader(event, 'Authorization');

10
src/server/utils/session.ts

@ -8,7 +8,10 @@ export type WGSession = Partial<{
const name = 'wg-easy';
export async function useWGSession(event: H3Event, rememberMe = false) {
const sessionConfig = await Database.general.getSessionConfig();
const sessionConfig = applySessionOverrides(
await Database.general.getSessionConfig()
);
return useSession<WGSession>(event, {
password: sessionConfig.sessionPassword,
name,
@ -22,7 +25,10 @@ export async function useWGSession(event: H3Event, rememberMe = false) {
}
export async function getWGSession(event: H3Event) {
const sessionConfig = await Database.general.getSessionConfig();
const sessionConfig = applySessionOverrides(
await Database.general.getSessionConfig()
);
return getSession<WGSession>(event, {
password: sessionConfig.sessionPassword,
name,

Loading…
Cancel
Save