Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da61cb680f | |||
| 8b5e6c4c7a | |||
| e192855280 | |||
| 3a52e1321b | |||
| b29703af86 | |||
| 516a3fa72c | |||
| e5cdb2b57a | |||
| 7fbc1cef68 | |||
| 432e7a8197 | |||
| d12045e10c | |||
| 015f3c5ba2 | |||
| e42f7313ab | |||
| 993c130f65 | |||
| fbf24410db | |||
| c1d5822f41 | |||
| 268916782d | |||
| 11ab71b5d2 | |||
| 67185192fd | |||
| 5fd3ee9843 | |||
| e444936c04 | |||
| 5d6c35b183 |
@@ -20,3 +20,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.
|
||||
|
||||
///
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -35,7 +42,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;
|
||||
|
||||
@@ -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>({
|
||||
|
||||
@@ -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>({
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6,11 +6,22 @@
|
||||
<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>
|
||||
<BaseSwitch :id="id" v-model="data" />
|
||||
</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>
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -6,21 +6,25 @@
|
||||
id="PreUp"
|
||||
v-model="data.preUp"
|
||||
:label="$t('hooks.preUp')"
|
||||
:overridden="overrides?.preUp"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PostUp"
|
||||
v-model="data.postUp"
|
||||
:label="$t('hooks.postUp')"
|
||||
:overridden="overrides?.postUp"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PreDown"
|
||||
v-model="data.preDown"
|
||||
:label="$t('hooks.preDown')"
|
||||
:overridden="overrides?.preDown"
|
||||
/>
|
||||
<FormTextField
|
||||
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(
|
||||
|
||||
@@ -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">
|
||||
@@ -164,6 +167,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(
|
||||
|
||||
@@ -55,10 +55,16 @@ const _submit = useSubmit(
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
revert: async (success) => {
|
||||
revert: async (success, data) => {
|
||||
if (success) {
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ export default definePermissionEventHandler(
|
||||
event,
|
||||
validateZod(InterfaceCidrUpdateSchema, event)
|
||||
);
|
||||
|
||||
await Database.interfaces.updateCidr(data);
|
||||
await WireGuard.saveConfig();
|
||||
return { success: true };
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -8,6 +8,19 @@ export default defineSetupEventHandler(2, async ({ event }) => {
|
||||
|
||||
await Database.users.create(username, password);
|
||||
|
||||
// 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 };
|
||||
}
|
||||
|
||||
return { success: true, setupDone: setupDone };
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -101,23 +101,27 @@ 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);
|
||||
|
||||
DB_DEBUG('Setting initial host and port...');
|
||||
await db.userConfigs.updateHostPort(
|
||||
WG_INITIAL_ENV.HOST,
|
||||
WG_INITIAL_ENV.PORT
|
||||
);
|
||||
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(host, port);
|
||||
|
||||
// Setup completion requires USERNAME and PASSWORD (no overrides for these)
|
||||
if (WG_INITIAL_ENV.USERNAME && WG_INITIAL_ENV.PASSWORD) {
|
||||
await db.general.setSetupStep(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function disableIpv6(db: DBType) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,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);
|
||||
}
|
||||
@@ -25,7 +28,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(
|
||||
@@ -150,8 +153,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);
|
||||
|
||||
@@ -217,25 +224,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.`
|
||||
);
|
||||
|
||||
WG_DEBUG('Starting Cron Job...');
|
||||
await this.startCronJob();
|
||||
@@ -254,12 +269,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,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];
|
||||
|
||||
@@ -63,3 +135,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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user