mirror of
https://github.com/wg-easy/wg-easy.git
synced 2025-09-26 19:51:15 +08:00
Feat: Initial Setup through env vars (#1736)
* initial support for initial setup * improve setup * improve mobile view * move base admin route * admin panel mobile view * set initial host and port * add docs * properly setup everything, use for dev env * change userconfig and interface port on setup, note users afterwards
This commit is contained in:
@@ -47,6 +47,7 @@ ENV DEBUG=Server,WireGuard,Database,CMD
|
||||
ENV PORT=51821
|
||||
ENV HOST=0.0.0.0
|
||||
ENV INSECURE=false
|
||||
ENV INIT_ENABLED=false
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/wg-easy/wg-easy
|
||||
|
||||
|
@@ -26,7 +26,8 @@ RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tab
|
||||
ENV DEBUG=Server,WireGuard,Database,CMD
|
||||
ENV PORT=51821
|
||||
ENV HOST=0.0.0.0
|
||||
ENV INSECURE=false
|
||||
ENV INSECURE=true
|
||||
ENV INIT_ENABLED=false
|
||||
|
||||
# Install Dependencies
|
||||
COPY src/package.json src/pnpm-lock.yaml ./
|
||||
|
@@ -15,6 +15,12 @@ services:
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
environment:
|
||||
- INIT_ENABLED=true
|
||||
- INIT_HOST=test
|
||||
- INIT_PORT=51820
|
||||
- INIT_USERNAME=testtest
|
||||
- INIT_PASSWORD=Qweasdyxcv!2
|
||||
|
||||
# folders should be generated inside container
|
||||
volumes:
|
||||
|
32
docs/content/advanced/config/unattended-setup.md
Normal file
32
docs/content/advanced/config/unattended-setup.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Unattended Setup
|
||||
---
|
||||
|
||||
If you want to run the setup without any user interaction, e.g. with a tool like Ansible, you can use these environment variables to configure the setup.
|
||||
|
||||
These will only be used during the first start of the container. After that, the setup will be disabled.
|
||||
|
||||
| Env | Example | Description | Group |
|
||||
| ---------------- | ----------------- | --------------------------------------------------------- | ----- |
|
||||
| `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 |
|
||||
|
||||
/// 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`
|
||||
///
|
||||
|
||||
/// note | Security
|
||||
|
||||
The initial username and password is not checked for complexity. Make sure to set a long enough username and a secure password. Otherwise, the user won't be able to log in.
|
||||
|
||||
Its recommended to remove the variables after the setup is done to prevent the password from being exposed.
|
||||
///
|
@@ -1,14 +1,17 @@
|
||||
<template>
|
||||
<TooltipProvider>
|
||||
<TooltipRoot>
|
||||
<TooltipRoot :open="open" @update:open="open = $event">
|
||||
<TooltipTrigger
|
||||
class="inline-flex h-8 w-8 items-center justify-center rounded-full text-gray-400 outline-none focus:shadow-sm focus:shadow-black"
|
||||
class="mx-2 inline-flex h-4 w-4 items-center justify-center rounded-full text-gray-400 outline-none focus:shadow-sm focus:shadow-black"
|
||||
as-child
|
||||
>
|
||||
<slot />
|
||||
<button @click="open = !open">
|
||||
<slot />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent
|
||||
class="select-none rounded bg-gray-600 px-3 py-2 text-sm leading-none text-white shadow-lg will-change-[transform,opacity]"
|
||||
class="select-none whitespace-pre-line rounded bg-gray-600 px-3 py-2 text-sm leading-none text-white shadow-lg will-change-[transform,opacity]"
|
||||
:side-offset="5"
|
||||
>
|
||||
{{ text }}
|
||||
@@ -21,4 +24,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ text: string }>();
|
||||
|
||||
const open = ref(false);
|
||||
</script>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<section class="grid grid-cols-2 gap-4">
|
||||
<slot />
|
||||
<Separator
|
||||
decorative
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<NuxtLink to="/" class="mb-4 flex-grow self-start">
|
||||
<NuxtLink to="/" class="mb-4">
|
||||
<h1 class="text-4xl font-medium dark:text-neutral-200">
|
||||
<img
|
||||
src="/logo.png"
|
||||
|
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="globalStore.release?.updateAvailable"
|
||||
v-if="
|
||||
globalStore.release?.updateAvailable &&
|
||||
authStore.userData &&
|
||||
hasPermissions(authStore.userData, 'admin', 'any')
|
||||
"
|
||||
class="font-small mb-10 rounded-md bg-red-800 p-4 text-sm text-white shadow-lg dark:bg-red-100 dark:text-red-600"
|
||||
:title="`v${globalStore.release.currentRelease} → v${globalStore.release.latestRelease.version}`"
|
||||
>
|
||||
@@ -23,6 +27,5 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
const globalStore = useGlobalStore();
|
||||
|
||||
// TODO: only show this to admins
|
||||
const authStore = useAuthStore();
|
||||
</script>
|
||||
|
@@ -1,23 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<header class="container mx-auto mt-4 max-w-3xl px-3 xs:mt-6 md:px-0">
|
||||
<header class="mx-auto mt-4 flex max-w-3xl flex-col justify-center">
|
||||
<div
|
||||
class="mb-5"
|
||||
class="mb-5 w-full"
|
||||
:class="
|
||||
loggedIn
|
||||
? 'flex flex-auto flex-col-reverse items-center gap-3 xxs:flex-row'
|
||||
? 'flex flex-col items-center justify-between sm:flex-row'
|
||||
: 'flex justify-end'
|
||||
"
|
||||
>
|
||||
<HeaderLogo v-if="loggedIn" />
|
||||
<div class="flex grow-0 items-center gap-3 self-end xxs:self-center">
|
||||
<div class="flex flex-row gap-3">
|
||||
<HeaderLangSelector />
|
||||
<HeaderThemeSwitch />
|
||||
<HeaderChartToggle v-if="loggedIn" />
|
||||
<UiUserMenu v-if="loggedIn" />
|
||||
</div>
|
||||
</div>
|
||||
<HeaderUpdate class="mt-5" />
|
||||
<HeaderUpdate class="mt-4" />
|
||||
</header>
|
||||
<slot />
|
||||
<UiFooter />
|
||||
|
@@ -11,8 +11,8 @@
|
||||
</header>
|
||||
<main>
|
||||
<Panel>
|
||||
<PanelBody class="mx-auto mt-10 p-4 md:w-[70%] lg:w-[60%]">
|
||||
<h2 class="mb-16 mt-8 text-3xl font-medium">
|
||||
<PanelBody class="m-4 mx-auto mt-10 md:w-[70%] lg:w-[60%]">
|
||||
<h2 class="mb-16 mt-8 text-center text-3xl font-medium">
|
||||
{{ $t('setup.welcome') }}
|
||||
</h2>
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container mx-auto p-4">
|
||||
<div class="flex">
|
||||
<div class="mr-4 w-64 rounded-lg bg-white p-4 dark:bg-neutral-700">
|
||||
<div class="flex flex-col gap-4 lg:flex-row">
|
||||
<div class="rounded-lg bg-white p-4 lg:w-64 dark:bg-neutral-700">
|
||||
<NuxtLink to="/admin">
|
||||
<h2 class="mb-4 text-xl font-bold dark:text-neutral-200">
|
||||
{{ t('pages.admin.panel') }}
|
||||
@@ -13,6 +13,7 @@
|
||||
v-for="(item, index) in menuItems"
|
||||
:key="index"
|
||||
:to="`/admin/${item.id}`"
|
||||
active-class="bg-red-800 rounded"
|
||||
>
|
||||
<BaseButton
|
||||
as="span"
|
||||
@@ -27,7 +28,7 @@
|
||||
<div
|
||||
class="flex-1 rounded-lg bg-white p-6 dark:bg-neutral-700 dark:text-neutral-200"
|
||||
>
|
||||
<h1 class="mb-6 text-3xl font-bold">{{ activeMenuItem?.name }}</h1>
|
||||
<h1 class="mb-6 text-3xl font-bold">{{ activeMenuItem.name }}</h1>
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,13 +45,17 @@ const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const menuItems = [
|
||||
{ id: '', name: t('pages.admin.general') },
|
||||
{ id: 'general', name: t('pages.admin.general') },
|
||||
{ id: 'config', name: t('pages.admin.config') },
|
||||
{ id: 'interface', name: t('pages.admin.interface') },
|
||||
{ id: 'hooks', name: t('pages.admin.hooks') },
|
||||
];
|
||||
|
||||
const defaultItem = { id: '', name: t('pages.admin.panel') };
|
||||
|
||||
const activeMenuItem = computed(() => {
|
||||
return menuItems.find((item) => route.path === `/admin/${item.id}`);
|
||||
return (
|
||||
menuItems.find((item) => route.path === `/admin/${item.id}`) ?? defaultItem
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
64
src/app/pages/admin/general.vue
Normal file
64
src/app/pages/admin/general.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<main v-if="data">
|
||||
<FormElement @submit.prevent="submit">
|
||||
<FormGroup>
|
||||
<FormNumberField
|
||||
id="session"
|
||||
v-model="data.sessionTimeout"
|
||||
:label="$t('admin.general.sessionTimeout')"
|
||||
:description="$t('admin.general.sessionTimeoutDesc')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading>{{ $t('admin.general.metrics') }}</FormHeading>
|
||||
<FormNullTextField
|
||||
id="password"
|
||||
v-model="data.metricsPassword"
|
||||
:label="$t('admin.general.metricsPassword')"
|
||||
:description="$t('admin.general.metricsPasswordDesc')"
|
||||
/>
|
||||
<FormSwitchField
|
||||
id="prometheus"
|
||||
v-model="data.metricsPrometheus"
|
||||
:label="$t('admin.general.prometheus')"
|
||||
:description="$t('admin.general.prometheusDesc')"
|
||||
/>
|
||||
<FormSwitchField
|
||||
id="json"
|
||||
v-model="data.metricsJson"
|
||||
:label="$t('admin.general.json')"
|
||||
:description="$t('admin.general.jsonDesc')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
||||
<FormActionField type="submit" :label="$t('form.save')" />
|
||||
<FormActionField :label="$t('form.revert')" @click="revert" />
|
||||
</FormGroup>
|
||||
</FormElement>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data: _data, refresh } = await useFetch(`/api/admin/general`, {
|
||||
method: 'get',
|
||||
});
|
||||
const data = toRef(_data.value);
|
||||
|
||||
const _submit = useSubmit(
|
||||
`/api/admin/general`,
|
||||
{
|
||||
method: 'post',
|
||||
},
|
||||
{ revert }
|
||||
);
|
||||
|
||||
function submit() {
|
||||
return _submit(data.value);
|
||||
}
|
||||
|
||||
async function revert() {
|
||||
await refresh();
|
||||
data.value = toRef(_data.value).value;
|
||||
}
|
||||
</script>
|
@@ -1,64 +1,5 @@
|
||||
<template>
|
||||
<main v-if="data">
|
||||
<FormElement @submit.prevent="submit">
|
||||
<FormGroup>
|
||||
<FormNumberField
|
||||
id="session"
|
||||
v-model="data.sessionTimeout"
|
||||
:label="$t('admin.general.sessionTimeout')"
|
||||
:description="$t('admin.general.sessionTimeoutDesc')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading>{{ $t('admin.general.metrics') }}</FormHeading>
|
||||
<FormNullTextField
|
||||
id="password"
|
||||
v-model="data.metricsPassword"
|
||||
:label="$t('admin.general.metricsPassword')"
|
||||
:description="$t('admin.general.metricsPasswordDesc')"
|
||||
/>
|
||||
<FormSwitchField
|
||||
id="prometheus"
|
||||
v-model="data.metricsPrometheus"
|
||||
:label="$t('admin.general.prometheus')"
|
||||
:description="$t('admin.general.prometheusDesc')"
|
||||
/>
|
||||
<FormSwitchField
|
||||
id="json"
|
||||
v-model="data.metricsJson"
|
||||
:label="$t('admin.general.json')"
|
||||
:description="$t('admin.general.jsonDesc')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
||||
<FormActionField type="submit" :label="$t('form.save')" />
|
||||
<FormActionField :label="$t('form.revert')" @click="revert" />
|
||||
</FormGroup>
|
||||
</FormElement>
|
||||
<main class="flex flex-col gap-3">
|
||||
<p class="whitespace-pre-line">{{ $t('admin.introText') }}</p>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data: _data, refresh } = await useFetch(`/api/admin/general`, {
|
||||
method: 'get',
|
||||
});
|
||||
const data = toRef(_data.value);
|
||||
|
||||
const _submit = useSubmit(
|
||||
`/api/admin/general`,
|
||||
{
|
||||
method: 'post',
|
||||
},
|
||||
{ revert }
|
||||
);
|
||||
|
||||
function submit() {
|
||||
return _submit(data.value);
|
||||
}
|
||||
|
||||
async function revert() {
|
||||
await refresh();
|
||||
data.value = toRef(_data.value).value;
|
||||
}
|
||||
</script>
|
||||
|
@@ -54,6 +54,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const authStore = useAuthStore();
|
||||
authStore.update();
|
||||
|
||||
const authenticating = ref(false);
|
||||
const remember = ref(false);
|
||||
const username = ref<null | string>(null);
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="px-8 pt-8 text-center text-2xl">
|
||||
<div class="flex flex-col items-center">
|
||||
<p class="px-8 text-center text-2xl">
|
||||
{{ $t('setup.welcomeDesc') }}
|
||||
</p>
|
||||
<NuxtLink to="/setup/2">
|
||||
<NuxtLink to="/setup/2" class="mt-8">
|
||||
<BaseButton as="span">{{ $t('general.continue') }}</BaseButton>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="p-8 text-center text-lg">
|
||||
<p class="text-center text-lg">
|
||||
{{ $t('setup.createAdminDesc') }}
|
||||
</p>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="mt-8 flex flex-col gap-3">
|
||||
<div class="flex flex-col">
|
||||
<FormNullTextField
|
||||
id="username"
|
||||
@@ -28,7 +28,7 @@
|
||||
:label="$t('general.confirmPassword')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mt-4 flex justify-center">
|
||||
<BaseButton @click="submit">{{ $t('setup.createAccount') }}</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,14 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="p-8 text-center text-lg">
|
||||
<p class="text-center text-lg">
|
||||
{{ $t('setup.existingSetup') }}
|
||||
</p>
|
||||
<div class="mb-8 flex justify-center">
|
||||
<NuxtLink to="/setup/4">
|
||||
<BaseButton as="span">{{ $t('general.no') }}</BaseButton>
|
||||
<div class="mt-4 flex justify-center gap-3">
|
||||
<NuxtLink to="/setup/4" class="w-20">
|
||||
<BaseButton as="span" class="w-full justify-center">
|
||||
{{ $t('general.no') }}
|
||||
</BaseButton>
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/setup/migrate">
|
||||
<BaseButton as="span">{{ $t('general.yes') }}</BaseButton>
|
||||
<NuxtLink to="/setup/migrate" class="w-20">
|
||||
<BaseButton as="span" class="w-full justify-center">
|
||||
{{ $t('general.yes') }}
|
||||
</BaseButton>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,21 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="p-8 text-center text-lg">
|
||||
<p class="text-center text-lg">
|
||||
{{ $t('setup.setupConfigDesc') }}
|
||||
</p>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="mt-8 flex flex-col gap-3">
|
||||
<div class="flex flex-col">
|
||||
<FormNullTextField
|
||||
id="host"
|
||||
v-model="host"
|
||||
:label="$t('general.host')"
|
||||
placeholder="vpn.example.com"
|
||||
:description="$t('setup.hostDesc')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<FormNumberField id="port" v-model="port" :label="$t('general.port')" />
|
||||
<FormNumberField
|
||||
id="port"
|
||||
v-model="port"
|
||||
:label="$t('general.port')"
|
||||
:description="$t('setup.portDesc')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mt-4 flex justify-center">
|
||||
<BaseButton @click="submit">{{ $t('general.continue') }}</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,13 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="p-8 text-center text-lg">
|
||||
<div class="flex flex-col items-center">
|
||||
<p class="text-center text-lg">
|
||||
{{ $t('setup.setupMigrationDesc') }}
|
||||
</p>
|
||||
<div>
|
||||
<div class="mt-8 flex gap-3">
|
||||
<Label for="migration">{{ $t('setup.migration') }}</Label>
|
||||
<input id="migration" type="file" @change="onChangeFile" />
|
||||
</div>
|
||||
<BaseButton @click="submit">{{ $t('setup.upload') }}</BaseButton>
|
||||
<div class="mt-4">
|
||||
<BaseButton @click="submit">{{ $t('setup.upload') }}</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col items-center">
|
||||
<p>{{ $t('setup.successful') }}</p>
|
||||
<NuxtLink to="/login">
|
||||
<NuxtLink to="/login" class="mt-4">
|
||||
<BaseButton as="span">{{ $t('login.signIn') }}</BaseButton>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
@@ -35,16 +35,18 @@
|
||||
"confirmPassword": "Confirm Password"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "Welcome to your first setup of wg-easy !",
|
||||
"welcomeDesc": "You have found the easiest way to install and manage WireGuard on any Linux host!",
|
||||
"welcome": "Welcome to your first setup of wg-easy",
|
||||
"welcomeDesc": "You have found the easiest way to install and manage WireGuard on any Linux host",
|
||||
"existingSetup": "Do you have an existing setup?",
|
||||
"createAdminDesc": "Please first enter an admin username and a strong secure password. This information will be used to log in to your administration panel.",
|
||||
"setupConfigDesc": "Please enter the host and port information. This will be used for the client configuration when setting up WireGuard on their devices.",
|
||||
"setupMigrationDesc": "Please provide the backup file if you want to migrate your data from your previous wg-easy version to your new setup.",
|
||||
"upload": "Upload",
|
||||
"migration": "Restore the backup",
|
||||
"migration": "Restore the backup:",
|
||||
"createAccount": "Create Account",
|
||||
"successful": "Setup successful"
|
||||
"successful": "Setup successful",
|
||||
"hostDesc": "Public hostname clients will connect to",
|
||||
"portDesc": "Public UDP port clients will connect to and WireGuard will listen on"
|
||||
},
|
||||
"update": {
|
||||
"updateAvailable": "There is an update available!",
|
||||
@@ -141,7 +143,7 @@
|
||||
"config": {
|
||||
"connection": "Connection",
|
||||
"hostDesc": "Public hostname clients will connect to (invalidates config)",
|
||||
"portDesc": "Public UDP port clients will connect to (invalidates config)",
|
||||
"portDesc": "Public UDP port clients will connect to (invalidates config, you probably want to change Interface Port too)",
|
||||
"allowedIpsDesc": "Allowed IPs clients will use (global config)",
|
||||
"dnsDesc": "DNS server clients will use (global config)",
|
||||
"mtuDesc": "MTU clients will use (only for new clients)",
|
||||
@@ -153,9 +155,10 @@
|
||||
"device": "Device",
|
||||
"deviceDesc": "Ethernet device the wireguard traffic should be forwarded through",
|
||||
"mtuDesc": "MTU WireGuard will use",
|
||||
"portDesc": "UDP Port WireGuard will listen on (could invalidate config)",
|
||||
"portDesc": "UDP Port WireGuard will listen on (you probably want to change Config Port too)",
|
||||
"changeCidr": "Change CIDR"
|
||||
}
|
||||
},
|
||||
"introText": "Welcome to the admin panel.\n\nHere you can manage the general settings, the configuration, the interface settings and the hooks.\n\nStart by choosing one of the sections in the sidebar."
|
||||
},
|
||||
"zod": {
|
||||
"generic": {
|
||||
|
@@ -28,7 +28,9 @@ export default defineNuxtConfig({
|
||||
},
|
||||
locales: [
|
||||
{
|
||||
// same as i18n.config.ts
|
||||
code: 'en',
|
||||
// BCP 47 language tag
|
||||
language: 'en-US',
|
||||
name: 'English',
|
||||
},
|
||||
|
@@ -2,11 +2,10 @@ export default defineEventHandler(async (event) => {
|
||||
const session = await useWGSession(event);
|
||||
|
||||
if (!session.data.userId) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Not logged in',
|
||||
});
|
||||
// not logged in
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await Database.users.get(session.data.userId);
|
||||
if (!user) {
|
||||
throw createError({
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
import { userConfig } from './schema';
|
||||
import type { UserConfigUpdateType } from './types';
|
||||
import { wgInterface } from '#db/schema';
|
||||
import type { DBType } from '#db/sqlite';
|
||||
|
||||
function createPreparedStatement(db: DBType) {
|
||||
@@ -8,14 +9,6 @@ function createPreparedStatement(db: DBType) {
|
||||
get: db.query.userConfig
|
||||
.findFirst({ where: eq(userConfig.id, sql.placeholder('interface')) })
|
||||
.prepare(),
|
||||
updateHostPort: db
|
||||
.update(userConfig)
|
||||
.set({
|
||||
host: sql.placeholder('host') as never as string,
|
||||
port: sql.placeholder('port') as never as number,
|
||||
})
|
||||
.where(eq(userConfig.id, sql.placeholder('interface')))
|
||||
.prepare(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,11 +31,26 @@ export class UserConfigService {
|
||||
return userConfig;
|
||||
}
|
||||
|
||||
// TODO: wrap ipv6 host in square brackets
|
||||
|
||||
/**
|
||||
* sets host of user config
|
||||
*
|
||||
* sets port of user config and interface
|
||||
*/
|
||||
updateHostPort(host: string, port: number) {
|
||||
return this.#statements.updateHostPort.execute({
|
||||
interface: 'wg0',
|
||||
host,
|
||||
port,
|
||||
return this.#db.transaction(async (tx) => {
|
||||
await tx
|
||||
.update(userConfig)
|
||||
.set({ host, port })
|
||||
.where(eq(userConfig.id, 'wg0'))
|
||||
.execute();
|
||||
|
||||
await tx
|
||||
.update(wgInterface)
|
||||
.set({ port })
|
||||
.where(eq(wgInterface.name, 'wg0'))
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,13 @@ const db = drizzle({ client, schema });
|
||||
|
||||
export async function connect() {
|
||||
await migrate();
|
||||
return new DBService(db);
|
||||
const dbService = new DBService(db);
|
||||
|
||||
if (WG_INITIAL_ENV.ENABLED) {
|
||||
await initialSetup(dbService);
|
||||
}
|
||||
|
||||
return dbService;
|
||||
}
|
||||
|
||||
class DBService {
|
||||
@@ -58,3 +64,47 @@ async function migrate() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function initialSetup(db: DBServiceType) {
|
||||
const setup = await db.general.getSetupStep();
|
||||
|
||||
if (setup.done) {
|
||||
DB_DEBUG('Setup already done. Skiping initial setup.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (WG_INITIAL_ENV.IPV4_CIDR && WG_INITIAL_ENV.IPV6_CIDR) {
|
||||
DB_DEBUG('Setting initial CIDR...');
|
||||
await db.interfaces.updateCidr({
|
||||
ipv4Cidr: WG_INITIAL_ENV.IPV4_CIDR,
|
||||
ipv6Cidr: WG_INITIAL_ENV.IPV6_CIDR,
|
||||
});
|
||||
}
|
||||
|
||||
if (WG_INITIAL_ENV.DNS) {
|
||||
DB_DEBUG('Setting initial DNS...');
|
||||
const userConfig = await db.userConfigs.get();
|
||||
await db.userConfigs.update({
|
||||
...userConfig,
|
||||
defaultDns: WG_INITIAL_ENV.DNS,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
WG_INITIAL_ENV.USERNAME &&
|
||||
WG_INITIAL_ENV.PASSWORD &&
|
||||
WG_INITIAL_ENV.HOST &&
|
||||
WG_INITIAL_ENV.PORT
|
||||
) {
|
||||
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(0);
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,19 @@ export const WG_ENV = {
|
||||
PORT: assertEnv('PORT'),
|
||||
};
|
||||
|
||||
export const WG_INITIAL_ENV = {
|
||||
ENABLED: process.env.INIT_ENABLED === 'true',
|
||||
USERNAME: process.env.INIT_USERNAME,
|
||||
PASSWORD: process.env.INIT_PASSWORD,
|
||||
DNS: process.env.INIT_DNS?.split(',').map((x) => x.trim()),
|
||||
IPV4_CIDR: process.env.INIT_IPV4_CIDR,
|
||||
IPV6_CIDR: process.env.INIT_IPV6_CIDR,
|
||||
HOST: process.env.INIT_HOST,
|
||||
PORT: process.env.INIT_PORT
|
||||
? Number.parseInt(process.env.INIT_PORT, 10)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
function assertEnv<T extends string>(env: T) {
|
||||
const val = process.env[env];
|
||||
|
||||
|
Reference in New Issue
Block a user