Improve wg-ui host setup: IP availability checks and current-user install script
- setup-main-host-button: live availability status, grab-next-subnet helper, error/success feedback - setup-wireguard-host: harden generated shell scripts with set -e - check-private-ip-address-availability: add user access check, drop manual body validation - setup-main-host: pass host object with wg_ip_address - install-wg-ui.sh: run service as the invoking user (root or sudo) instead of a dedicated wgui user, and skip systemd/OpenRC daemon setup when NODE_ENV=development
This commit is contained in:
@@ -1,77 +1,229 @@
|
|||||||
import { useEffect, useState, type ComponentProps } from "react";
|
import { useEffect, useRef, useState, type ComponentProps } from "react";
|
||||||
import Button from "../twui/layout/Button";
|
import Button from "../twui/layout/Button";
|
||||||
import useStatus from "../twui/hooks/useStatus";
|
import useStatus from "../twui/hooks/useStatus";
|
||||||
import fetchApi from "../twui/utils/fetch/fetchApi";
|
import fetchApi from "../twui/utils/fetch/fetchApi";
|
||||||
import type { ApiReqParams } from "@/src/types";
|
import type { ApiReqParams } from "@/src/types";
|
||||||
import Row from "../twui/layout/Row";
|
import Row from "../twui/layout/Row";
|
||||||
import Input from "../twui/form/Input";
|
import Input from "../twui/form/Input";
|
||||||
import Stack from "../twui/layout/Stack";
|
import Stack from "../twui/layout/Stack";
|
||||||
import { Network } from "lucide-react";
|
import {
|
||||||
import Span from "../twui/layout/Span";
|
CircleCheck,
|
||||||
import { AppData } from "@/src/data/app-data";
|
Loader2,
|
||||||
|
Network,
|
||||||
type Props = {
|
TriangleAlert,
|
||||||
button_props?: Omit<ComponentProps<typeof Button>, "title">;
|
Wand2,
|
||||||
};
|
} from "lucide-react";
|
||||||
|
import Span from "../twui/layout/Span";
|
||||||
export default function SetupMainHostButton({ button_props }: Props) {
|
import Tag from "../twui/elements/Tag";
|
||||||
const { loading, setLoading, ready, setReady } = useStatus();
|
import { AppData } from "@/src/data/app-data";
|
||||||
|
|
||||||
const [wgIP, setWgIP] = useState<string>(AppData["DefaultPrivateIP"]);
|
type Props = {
|
||||||
|
button_props?: Omit<ComponentProps<typeof Button>, "title">;
|
||||||
useEffect(() => {
|
};
|
||||||
fetchApi<ApiReqParams>(
|
|
||||||
`/api/admin/check-private-ip-address-availability`,
|
type AvailabilityType = {
|
||||||
{
|
success: boolean;
|
||||||
method: "POST",
|
};
|
||||||
body: { ip_address: wgIP },
|
|
||||||
},
|
type GrabSubnetType = {
|
||||||
).then((res) => {
|
success: boolean;
|
||||||
console.log(`res`, res);
|
msg?: string;
|
||||||
});
|
};
|
||||||
}, [wgIP]);
|
|
||||||
|
const subnet_ip_pattern = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.1$/;
|
||||||
return (
|
|
||||||
<Stack className="w-full items-stretch">
|
type IPStatusType = "checking" | "available" | "not_available" | "invalid";
|
||||||
<Input
|
|
||||||
defaultValue={wgIP}
|
export default function SetupMainHostButton({ button_props }: Props) {
|
||||||
changeHandler={(v) => {
|
const { loading, setLoading, status, setStatus } = useStatus();
|
||||||
setWgIP(v);
|
|
||||||
}}
|
const [wgIP, setWgIP] = useState<string>(AppData["DefaultPrivateIP"]);
|
||||||
prefix={<Network size={17} opacity={0.5} />}
|
const [availability, setAvailability] = useState<AvailabilityType>();
|
||||||
suffix={
|
const [grabbing, setGrabbing] = useState(false);
|
||||||
<Span className="text-sm opacity-50 whitespace-nowrap">
|
|
||||||
Selected Wireguard IP Address
|
const input_ref = useRef<HTMLInputElement>(null);
|
||||||
</Span>
|
|
||||||
}
|
useEffect(() => {
|
||||||
autoFocus
|
setAvailability(undefined);
|
||||||
/>
|
|
||||||
|
if (!subnet_ip_pattern.test(wgIP)) return;
|
||||||
<Button
|
|
||||||
title="Setup Main Host"
|
let cancelled = false;
|
||||||
{...button_props}
|
|
||||||
loading={loading}
|
fetchApi<ApiReqParams, AvailabilityType>(
|
||||||
onClick={() => {
|
`/api/admin/check-private-ip-address-availability`,
|
||||||
setLoading(true);
|
{
|
||||||
|
method: "POST",
|
||||||
fetchApi<ApiReqParams>(`/api/admin/setup-main-host`, {
|
body: { ip_address: wgIP },
|
||||||
method: "POST",
|
},
|
||||||
body: {
|
).then((res) => {
|
||||||
ip_address: ``,
|
if (cancelled) return;
|
||||||
},
|
setAvailability(res);
|
||||||
})
|
});
|
||||||
.then((res) => {
|
|
||||||
console.log(`res`, res);
|
return () => {
|
||||||
})
|
cancelled = true;
|
||||||
.finally(() => {
|
};
|
||||||
setTimeout(() => {
|
}, [wgIP]);
|
||||||
setLoading(false);
|
|
||||||
}, 4000);
|
const ip_status: IPStatusType = subnet_ip_pattern.test(wgIP)
|
||||||
});
|
? availability
|
||||||
}}
|
? availability.success
|
||||||
>
|
? "available"
|
||||||
Setup Main Host
|
: "not_available"
|
||||||
</Button>
|
: "checking"
|
||||||
</Stack>
|
: "invalid";
|
||||||
);
|
|
||||||
}
|
function grabNextSubnet() {
|
||||||
|
setGrabbing(true);
|
||||||
|
|
||||||
|
fetchApi<ApiReqParams, GrabSubnetType>(
|
||||||
|
`/api/admin/grab-next-available-private-ip`,
|
||||||
|
{ method: "POST" },
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
if (res?.success && res.msg) {
|
||||||
|
setWgIP(res.msg);
|
||||||
|
if (input_ref.current) {
|
||||||
|
input_ref.current.value = res.msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setGrabbing(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack className="w-full items-stretch gap-3">
|
||||||
|
<Stack className="w-full gap-2">
|
||||||
|
<Input
|
||||||
|
defaultValue={wgIP}
|
||||||
|
changeHandler={(v) => {
|
||||||
|
setWgIP(v);
|
||||||
|
}}
|
||||||
|
prefix={<Network size={17} opacity={0.5} />}
|
||||||
|
suffix={
|
||||||
|
<Span className="text-sm opacity-50 whitespace-nowrap">
|
||||||
|
Selected Wireguard IP Address
|
||||||
|
</Span>
|
||||||
|
}
|
||||||
|
componentRef={input_ref}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
{ip_status === "invalid" ? (
|
||||||
|
<Tag
|
||||||
|
variant="outlined"
|
||||||
|
color="error"
|
||||||
|
className="w-full py-1.5 outline-error/50 bg-error/5!"
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<TriangleAlert size={15} />
|
||||||
|
<span>Invalid IP</span>
|
||||||
|
</Row>
|
||||||
|
</Tag>
|
||||||
|
) : ip_status === "not_available" ? (
|
||||||
|
<Tag
|
||||||
|
variant="outlined"
|
||||||
|
color="error"
|
||||||
|
className="w-full py-1.5 outline-error/50 bg-error/5!"
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<TriangleAlert size={15} />
|
||||||
|
<span>Not available</span>
|
||||||
|
</Row>
|
||||||
|
</Tag>
|
||||||
|
) : ip_status === "available" ? (
|
||||||
|
<Tag
|
||||||
|
variant="outlined"
|
||||||
|
color="success"
|
||||||
|
className="w-full py-1.5 outline-success/50 bg-success/5!"
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<CircleCheck size={15} />
|
||||||
|
<span>Available</span>
|
||||||
|
</Row>
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag
|
||||||
|
variant="outlined"
|
||||||
|
color="gray"
|
||||||
|
className="w-full py-1.5 outline-gray/50 bg-gray/5!"
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<Loader2 size={15} className="animate-spin" />
|
||||||
|
<span>Checking availability…</span>
|
||||||
|
</Row>
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{status?.error && status.msg ? (
|
||||||
|
<Tag
|
||||||
|
variant="outlined"
|
||||||
|
color="error"
|
||||||
|
className="w-full py-1.5 outline-error/50 bg-error/5!"
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<TriangleAlert size={15} />
|
||||||
|
<span>{status.msg}</span>
|
||||||
|
</Row>
|
||||||
|
</Tag>
|
||||||
|
) : null}
|
||||||
|
{status?.success && status.msg ? (
|
||||||
|
<Tag
|
||||||
|
variant="outlined"
|
||||||
|
color="success"
|
||||||
|
className="w-full py-1.5 outline-success/50 bg-success/5!"
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
<CircleCheck size={15} />
|
||||||
|
<span>{status.msg}</span>
|
||||||
|
</Row>
|
||||||
|
</Tag>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Row className="w-full items-center gap-2">
|
||||||
|
<Button
|
||||||
|
title="Grab Next Available Subnet"
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
beforeIcon={<Wand2 size={16} />}
|
||||||
|
loading={grabbing}
|
||||||
|
onClick={grabNextSubnet}
|
||||||
|
>
|
||||||
|
Grab Next Subnet
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Setup Main Host"
|
||||||
|
{...button_props}
|
||||||
|
disabled={ip_status !== "available"}
|
||||||
|
loading={loading}
|
||||||
|
onClick={() => {
|
||||||
|
setLoading(true);
|
||||||
|
setStatus(undefined);
|
||||||
|
|
||||||
|
fetchApi<ApiReqParams>(`/api/admin/setup-main-host`, {
|
||||||
|
method: "POST",
|
||||||
|
body: { ip_address: wgIP },
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
setStatus({
|
||||||
|
success: res.success,
|
||||||
|
error: !res.success,
|
||||||
|
msg: res.msg,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Setup Main Host
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export default async function setupWireguardHost({
|
|||||||
|
|
||||||
let pre_sh = ``;
|
let pre_sh = ``;
|
||||||
|
|
||||||
|
pre_sh += `set -e\n`;
|
||||||
pre_sh += `cd ${WIREGUARD_HOST_CONFIG_DIR}\n`;
|
pre_sh += `cd ${WIREGUARD_HOST_CONFIG_DIR}\n`;
|
||||||
pre_sh += `if [ ! -f ${WIREGUARD_PRIVATE_KEY_FILE_NAME} ]; then\n`;
|
pre_sh += `if [ ! -f ${WIREGUARD_PRIVATE_KEY_FILE_NAME} ]; then\n`;
|
||||||
pre_sh += ` wg genkey | tee ${WIREGUARD_PRIVATE_KEY_FILE_NAME} | wg pubkey > ${WIREGUARD_PUBLIC_KEY_FILE_NAME}\n`;
|
pre_sh += ` wg genkey | tee ${WIREGUARD_PRIVATE_KEY_FILE_NAME} | wg pubkey > ${WIREGUARD_PUBLIC_KEY_FILE_NAME}\n`;
|
||||||
@@ -112,6 +113,8 @@ export default async function setupWireguardHost({
|
|||||||
|
|
||||||
let sh = ``;
|
let sh = ``;
|
||||||
|
|
||||||
|
sh += `set -e\n`;
|
||||||
|
|
||||||
const POST_UP_PATH = path.join(
|
const POST_UP_PATH = path.join(
|
||||||
WGUI_LIB_IP_TABLES_DIR,
|
WGUI_LIB_IP_TABLES_DIR,
|
||||||
`${host_id}-up.sh`,
|
`${host_id}-up.sh`,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
|
||||||
import userAuth from "@/src/functions/backend/auth/user-auth";
|
import userAuth from "@/src/functions/backend/auth/user-auth";
|
||||||
import checkPrivateIPAvailability from "@/src/functions/backend/setup/check-private-ip-availability";
|
import checkPrivateIPAvailability from "@/src/functions/backend/setup/check-private-ip-availability";
|
||||||
import type { ApiReqParams } from "@/src/types";
|
import type { ApiReqParams } from "@/src/types";
|
||||||
@@ -11,7 +12,7 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
|||||||
params,
|
params,
|
||||||
) => {
|
) => {
|
||||||
const req = params.req;
|
const req = params.req;
|
||||||
const body = params.body as ApiReqParams | undefined;
|
const body = params.body as ApiReqParams;
|
||||||
|
|
||||||
if (req.method !== "POST") {
|
if (req.method !== "POST") {
|
||||||
return {
|
return {
|
||||||
@@ -30,17 +31,13 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ip_address = body?.ip_address;
|
return await checkPrivateIPAvailability({
|
||||||
|
ip_address: body?.ip_address || "",
|
||||||
if (!ip_address) {
|
});
|
||||||
throw new Error(`No IP address passed!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await checkPrivateIPAvailability({ ip_address });
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
msg: error.message,
|
msg: error.message,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -41,7 +41,9 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await setupWireguardHost({
|
return await setupWireguardHost({
|
||||||
wg_subnet_ip: body.ip_address,
|
host: {
|
||||||
|
wg_ip_address: body.ip_address,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -10,20 +10,46 @@ set -euo pipefail
|
|||||||
|
|
||||||
WGUI_LIB_DIR="/var/lib/wgui"
|
WGUI_LIB_DIR="/var/lib/wgui"
|
||||||
INSTALL_DIR="${INSTALL_DIR:-$WGUI_LIB_DIR/webapp}"
|
INSTALL_DIR="${INSTALL_DIR:-$WGUI_LIB_DIR/webapp}"
|
||||||
SERVICE_USER="${SERVICE_USER:-wgui}"
|
SERVICE_USER="${SERVICE_USER:-}"
|
||||||
|
SERVICE_GROUP="${SERVICE_GROUP:-}"
|
||||||
SERVICE_NAME="${SERVICE_NAME:-wgui}"
|
SERVICE_NAME="${SERVICE_NAME:-wgui}"
|
||||||
REPO_URL="${REPO_URL:-}"
|
REPO_URL="${REPO_URL:-}"
|
||||||
BRANCH="${BRANCH:-main}"
|
BRANCH="${BRANCH:-main}"
|
||||||
BUN_INSTALL_DIR="${BUN_INSTALL_DIR:-/opt/bun}"
|
BUN_INSTALL_DIR="${BUN_INSTALL_DIR:-/opt/bun}"
|
||||||
BUN_BIN="/usr/local/bin/bun"
|
BUN_BIN="/usr/local/bin/bun"
|
||||||
UPDATE=false
|
UPDATE=false
|
||||||
|
DEV_MODE=false
|
||||||
|
if [ "${NODE_ENV:-}" = "development" ]; then
|
||||||
|
DEV_MODE=true
|
||||||
|
log "NODE_ENV=development — skipping system service installation (daemon is assumed to already be running)"
|
||||||
|
fi
|
||||||
|
|
||||||
log() { echo "==> $*"; }
|
log() { echo "==> $*"; }
|
||||||
fail() { echo "error: $*" >&2; exit 1; }
|
fail() { echo "error: $*" >&2; exit 1; }
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
require_root() {
|
||||||
fail "this script must be run as root"
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
fi
|
if ! command -v sudo >/dev/null 2>&1; then
|
||||||
|
fail "this script must be run as root, and sudo is not installed"
|
||||||
|
fi
|
||||||
|
log "not running as root — re-executing with sudo ..."
|
||||||
|
sudo -v || fail "current user does not have sudo privileges — run this script as root or grant the current user sudo access"
|
||||||
|
exec sudo -E "$0" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_service_user() {
|
||||||
|
if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ] && id -u "$SUDO_USER" >/dev/null 2>&1; then
|
||||||
|
echo "$SUDO_USER"
|
||||||
|
else
|
||||||
|
echo "root"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_root
|
||||||
|
SERVICE_USER="${SERVICE_USER:-$(resolve_service_user)}"
|
||||||
|
SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}"
|
||||||
|
log "running the wg-ui service as $SERVICE_USER (group: $SERVICE_GROUP)"
|
||||||
|
|
||||||
if [ -z "$REPO_URL" ]; then
|
if [ -z "$REPO_URL" ]; then
|
||||||
fail "REPO_URL is not set — pass the git URL of the wg-ui repo, e.g.
|
fail "REPO_URL is not set — pass the git URL of the wg-ui repo, e.g.
|
||||||
@@ -90,11 +116,8 @@ install_bun() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setup_service_user() {
|
setup_service_user() {
|
||||||
if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then
|
|
||||||
useradd --system --home-dir "$WGUI_LIB_DIR" --shell /usr/sbin/nologin "$SERVICE_USER"
|
|
||||||
fi
|
|
||||||
mkdir -p "$WGUI_LIB_DIR/iptables" "$WGUI_LIB_DIR/keys" "$WGUI_LIB_DIR/clients"
|
mkdir -p "$WGUI_LIB_DIR/iptables" "$WGUI_LIB_DIR/keys" "$WGUI_LIB_DIR/clients"
|
||||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$WGUI_LIB_DIR"
|
chown -R "$SERVICE_USER:$SERVICE_GROUP" "$WGUI_LIB_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
clone_or_update() {
|
clone_or_update() {
|
||||||
@@ -115,7 +138,7 @@ clone_or_update() {
|
|||||||
log "cloning wg-ui ($REPO_URL, branch $BRANCH) ..."
|
log "cloning wg-ui ($REPO_URL, branch $BRANCH) ..."
|
||||||
git clone --depth 1 --branch "$BRANCH" "$REPO_URL" "$INSTALL_DIR"
|
git clone --depth 1 --branch "$BRANCH" "$REPO_URL" "$INSTALL_DIR"
|
||||||
fi
|
fi
|
||||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR"
|
chown -R "$SERVICE_USER:$SERVICE_GROUP" "$INSTALL_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_as_service_user() {
|
run_as_service_user() {
|
||||||
@@ -137,12 +160,12 @@ ensure_env_file() {
|
|||||||
if [ ! -f "$INSTALL_DIR/.env" ]; then
|
if [ ! -f "$INSTALL_DIR/.env" ]; then
|
||||||
log "generating $INSTALL_DIR/.env with fresh encryption secrets ..."
|
log "generating $INSTALL_DIR/.env with fresh encryption secrets ..."
|
||||||
{
|
{
|
||||||
echo "NODE_ENV=production"
|
echo "NODE_ENV=${NODE_ENV:-production}"
|
||||||
echo "ENCRYPTION_KEY=$(openssl rand -base64 32 | tr -d '\n')"
|
echo "ENCRYPTION_KEY=$(openssl rand -base64 32 | tr -d '\n')"
|
||||||
echo "ENCRYPTION_SALT=$(openssl rand -base64 32 | tr -d '\n')"
|
echo "ENCRYPTION_SALT=$(openssl rand -base64 32 | tr -d '\n')"
|
||||||
echo "DATA_DIR=$INSTALL_DIR/.data"
|
echo "DATA_DIR=$INSTALL_DIR/.data"
|
||||||
} > "$INSTALL_DIR/.env"
|
} > "$INSTALL_DIR/.env"
|
||||||
chown "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR/.env"
|
chown "$SERVICE_USER:$SERVICE_GROUP" "$INSTALL_DIR/.env"
|
||||||
chmod 600 "$INSTALL_DIR/.env"
|
chmod 600 "$INSTALL_DIR/.env"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -158,7 +181,7 @@ setup_wireguard() {
|
|||||||
grant_runtime_access() {
|
grant_runtime_access() {
|
||||||
log "granting $SERVICE_USER access to /etc/wireguard ..."
|
log "granting $SERVICE_USER access to /etc/wireguard ..."
|
||||||
mkdir -p /etc/wireguard
|
mkdir -p /etc/wireguard
|
||||||
chown "root:$SERVICE_USER" /etc/wireguard
|
chown "root:$SERVICE_GROUP" /etc/wireguard
|
||||||
chmod 770 /etc/wireguard
|
chmod 770 /etc/wireguard
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +197,7 @@ Wants=network-online.target
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=$SERVICE_USER
|
User=$SERVICE_USER
|
||||||
Group=$SERVICE_USER
|
Group=$SERVICE_GROUP
|
||||||
WorkingDirectory=$INSTALL_DIR
|
WorkingDirectory=$INSTALL_DIR
|
||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
ExecStart=$BUN_BIN src/server.ts
|
ExecStart=$BUN_BIN src/server.ts
|
||||||
@@ -242,23 +265,29 @@ ensure_env_file
|
|||||||
setup_wireguard
|
setup_wireguard
|
||||||
grant_runtime_access
|
grant_runtime_access
|
||||||
|
|
||||||
case "$INIT_SYSTEM" in
|
if [ "$DEV_MODE" = false ]; then
|
||||||
systemd)
|
case "$INIT_SYSTEM" in
|
||||||
install_systemd_unit
|
systemd)
|
||||||
;;
|
install_systemd_unit
|
||||||
openrc)
|
;;
|
||||||
install_openrc_unit
|
openrc)
|
||||||
;;
|
install_openrc_unit
|
||||||
*)
|
;;
|
||||||
log "no supported init system found — start manually with:
|
*)
|
||||||
|
log "no supported init system found — start manually with:
|
||||||
su -s /bin/bash $SERVICE_USER -c 'cd $INSTALL_DIR && NODE_ENV=production $BUN_BIN src/server.ts'
|
su -s /bin/bash $SERVICE_USER -c 'cd $INSTALL_DIR && NODE_ENV=production $BUN_BIN src/server.ts'
|
||||||
(add the line above to your boot scripts)"
|
(add the line above to your boot scripts)"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
PORT="$(grab_port)"
|
PORT="$(grab_port)"
|
||||||
log "wg-ui install complete."
|
log "wg-ui install complete."
|
||||||
log "webapp: $INSTALL_DIR"
|
log "webapp: $INSTALL_DIR"
|
||||||
log "runtime: $WGUI_LIB_DIR (keys, iptables, client configs)"
|
log "runtime: $WGUI_LIB_DIR (keys, iptables, client configs)"
|
||||||
log "process: managed by $INIT_SYSTEM as $SERVICE_NAME"
|
if [ "$DEV_MODE" = true ]; then
|
||||||
|
log "process: development mode — no system service installed"
|
||||||
|
else
|
||||||
|
log "process: managed by $INIT_SYSTEM as $SERVICE_NAME"
|
||||||
|
fi
|
||||||
log "open http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):$PORT in your browser"
|
log "open http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):$PORT in your browser"
|
||||||
Reference in New Issue
Block a user