mirror of
https://github.com/wireadmin/wireadmin
synced 2025-04-25 16:39:58 +00:00
feat: dns server
This commit is contained in:
parent
ae787625b9
commit
06a0a3008b
5
.changeset/thick-moles-kick.md
Normal file
5
.changeset/thick-moles-kick.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"wireadmin": major
|
||||
---
|
||||
|
||||
feat: Creates a Dnsmasq server on port 53 and forwards DNS queries through the Tor network.
|
32
Dockerfile
32
Dockerfile
@ -7,17 +7,15 @@ ENV TZ=UTC
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add -U --no-cache \
|
||||
iptables net-tools \
|
||||
screen logrotate bash \
|
||||
wireguard-tools \
|
||||
dnsmasq \
|
||||
tor \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
FROM node as base
|
||||
RUN apk add -U --no-cache \
|
||||
iptables net-tools \
|
||||
screen bash \
|
||||
wireguard-tools \
|
||||
tor \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
FROM base AS build
|
||||
FROM node AS build
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
@ -29,28 +27,28 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
&& mv build /tmp/build \
|
||||
&& rm -rf ./*
|
||||
|
||||
FROM base
|
||||
FROM node
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=tor /usr/local/bin/lyrebird /usr/local/bin/lyrebird
|
||||
COPY rootfs /
|
||||
|
||||
COPY /config/torrc.template /etc/tor/torrc.template
|
||||
|
||||
# Base env
|
||||
ENV PROTOCOL_HEADER=x-forwarded-proto
|
||||
ENV HOST_HEADER=x-forwarded-host
|
||||
ENV NODE_ENV=production
|
||||
ENV LOG_LEVEL=error
|
||||
|
||||
# Copy the goods from the build stage
|
||||
# Copy the goodies from the build stage
|
||||
COPY --from=build /tmp/package.json package.json
|
||||
COPY --from=build /tmp/node_modules node_modules
|
||||
COPY --from=build /tmp/build build
|
||||
|
||||
# Fix permissions
|
||||
RUN mkdir -p /data && chmod 700 /data
|
||||
RUN mkdir -p /etc/tor/torrc.d && chmod -R 400 /etc/tor/torrc.d
|
||||
RUN mkdir -p /var/log/wireadmin && touch /var/log/wireadmin/web.log
|
||||
RUN mkdir -p /data/ && chmod 700 /data/
|
||||
RUN mkdir -p /etc/tor/torrc.d/ && chmod -R 400 /etc/tor/
|
||||
RUN mkdir -p /var/log/wireadmin/ && touch /var/log/wireadmin/web.log
|
||||
|
||||
RUN echo '* * * * * /usr/bin/env logrotate /etc/logrotate.d/rotator' > /etc/crontabs/root
|
||||
|
||||
# Setup entrypoint
|
||||
COPY docker-entrypoint.sh /entrypoint.sh
|
||||
|
@ -7,18 +7,15 @@ ENV TZ=UTC
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add -U --no-cache \
|
||||
iptables net-tools \
|
||||
screen logrotate bash \
|
||||
wireguard-tools \
|
||||
dnsmasq \
|
||||
tor \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
FROM node as base
|
||||
RUN apk add -U --no-cache \
|
||||
iptables net-tools \
|
||||
screen logrotate vim bash \
|
||||
wireguard-tools \
|
||||
tor &&\
|
||||
# Clear APK cache
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
FROM base
|
||||
FROM node
|
||||
WORKDIR /app
|
||||
|
||||
# Setup Pnpm
|
||||
@ -32,15 +29,12 @@ ENV PROTOCOL_HEADER=x-forwarded-proto
|
||||
ENV HOST_HEADER=x-forwarded-host
|
||||
|
||||
COPY --from=tor /usr/local/bin/lyrebird /usr/local/bin/lyrebird
|
||||
|
||||
# Copy Tor Configs
|
||||
COPY /config/torrc.template /etc/tor/torrc.template
|
||||
COPY /config/obfs4-bridges.conf /etc/tor/torrc.d/obfs4-bridges.conf
|
||||
COPY rootfs /
|
||||
|
||||
# Fix permissions
|
||||
RUN mkdir -p /data && chmod 700 /data
|
||||
RUN mkdir -p /etc/tor/torrc.d && chmod -R 400 /etc/tor/torrc.d
|
||||
RUN mkdir -p /var/log/wireadmin && touch /var/log/wireadmin/web.log
|
||||
RUN mkdir -p /data/ && chmod 700 /data/
|
||||
RUN mkdir -p /etc/tor/torrc.d/ && chmod -R 400 /etc/tor/
|
||||
RUN mkdir -p /var/log/wireadmin/ && touch /var/log/wireadmin/web.log
|
||||
|
||||
RUN echo '* * * * * /usr/bin/env logrotate /etc/logrotate.d/rotator' > /etc/crontabs/root
|
||||
|
||||
|
@ -1,51 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
source /etc/wireadmin/xscript.sh
|
||||
|
||||
ENV_FILE="/app/.env"
|
||||
|
||||
TOR_CONFIG="/etc/tor/torrc"
|
||||
TOR_CONFIG_TEMPLATE="${TOR_CONFIG}.template"
|
||||
|
||||
log() {
|
||||
echo "[$(date +"%Y-%m-%d %H:%M:%S")] [$1] $2"
|
||||
}
|
||||
|
||||
to_camel_case() {
|
||||
echo "${1}" | awk -F_ '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2));}1' OFS=""
|
||||
}
|
||||
|
||||
generate_tor_config() {
|
||||
# Copying the torrc template to the torrc file
|
||||
cp "${TOR_CONFIG_TEMPLATE}" "${TOR_CONFIG}"
|
||||
|
||||
# IP address of the container
|
||||
local inet_address="$(hostname -i | awk '{print $1}')"
|
||||
|
||||
sed -i "s/{{INET_ADDRESS}}/$inet_address/g" "${TOR_CONFIG}"
|
||||
|
||||
# any other environment variables that start with TOR_ are added to the torrc
|
||||
# file
|
||||
env | grep ^TOR_ | sed -e 's/TOR_//' -e 's/=/ /' | while read -r line; do
|
||||
key=$(echo "$line" | awk '{print $1}')
|
||||
value=$(echo "$line" | awk '{print $2}')
|
||||
key=$(to_camel_case "$key")
|
||||
echo "$key $value" >> "${TOR_CONFIG}"
|
||||
done
|
||||
|
||||
# Removing duplicated tor options
|
||||
awk -F= '!a[tolower($1)]++' "${TOR_CONFIG}" > "/tmp/$(basename "${TOR_CONFIG}")" \
|
||||
&& mv "/tmp/$(basename "${TOR_CONFIG}")" "${TOR_CONFIG}"
|
||||
|
||||
# Remove comment line with single Hash
|
||||
sed -i '/^#\([^#]\)/d' "${TOR_CONFIG}"
|
||||
# Remove options with no value. (KEY[:space:]{...VALUE})
|
||||
sed -i '/^[^ ]* $/d' "${TOR_CONFIG}"
|
||||
# Remove double empty lines
|
||||
sed -i '/^$/N;/^\n$/D' "${TOR_CONFIG}"
|
||||
|
||||
log "notice" "Tor configuration file has been generated"
|
||||
}
|
||||
|
||||
echo " "
|
||||
echo " _ ___ ___ __ _ "
|
||||
echo "| | / (_)_______ / | ____/ /___ ___ (_)___ "
|
||||
@ -72,33 +31,29 @@ fi
|
||||
|
||||
# Generate Tor configuration
|
||||
generate_tor_config
|
||||
setup_logrotate
|
||||
setup_dns
|
||||
|
||||
tee "/etc/logrotate.d/rotator" &> /dev/null << EOF
|
||||
/var/log/wireadmin/*.log {
|
||||
size 512K
|
||||
rotate 3
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 root adm
|
||||
copytruncate
|
||||
}
|
||||
EOF
|
||||
|
||||
# Start Tor on the background
|
||||
# Background services
|
||||
crond
|
||||
_TOR_UID=$(id -u tor)
|
||||
mkdir -p /var/lib/tor && chown -R "$_TOR_UID:$_TOR_UID" /var/lib/tor
|
||||
screen -L -Logfile /var/log/wireadmin/tor.log -dmS tor bash -c "screen -S tor -X wrap off; tor -f $TOR_CONFIG"
|
||||
dnsmasq
|
||||
|
||||
# Start Tor
|
||||
screen -L -Logfile /var/log/wireadmin/tor.log -dmS tor \
|
||||
bash -c "screen -S tor -X wrap off; tor -f $TOR_CONFIG"
|
||||
|
||||
sleep 1
|
||||
echo -e "\n======================== Versions ========================"
|
||||
echo -e "Alpine: \c" && cat /etc/alpine-release
|
||||
echo -e "WireGuard: \c" && wg -v | head -n 1 | awk '{print $2}'
|
||||
echo -e "Tor: \c" && tor --version | head -n 1 | cut -d ' ' -f3
|
||||
echo -e "Tor: \c" && tor --version | head -n 1 | awk '{print $3}' | sed 's/.$//'
|
||||
echo -e "Dnsmasq: \c" && dnsmasq -v | head -n 1 | cut -d ' ' -f3
|
||||
echo -e "Lyrebird: \c" && lyrebird -version
|
||||
echo -e "\n========================= Torrc ========================="
|
||||
echo -e "\n======================= Tor Config ======================="
|
||||
grep -v "^#" "$TOR_CONFIG"
|
||||
echo -e "========================================================\n"
|
||||
echo -e "====================== Dnsmasq Config ======================"
|
||||
grep -v "^#" "$DNSMASQ_CONFIG"
|
||||
echo -e "==========================================================\n"
|
||||
sleep 1
|
||||
|
||||
exec "$@"
|
||||
|
54
rootfs/etc/wireadmin/internal/dns.sh
Normal file
54
rootfs/etc/wireadmin/internal/dns.sh
Normal file
@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
|
||||
DNSMASQ_CONFIG=/etc/dnsmasq.d/tor-dns.conf
|
||||
|
||||
setup_dns() {
|
||||
local _TOR_DNS_PORT="$(get_torrc_option "DNSPort")"
|
||||
local _TOR_DNS_HOST="127.0.0.1"
|
||||
if [ -z "$_TOR_DNS_PORT" ]; then
|
||||
log ERROR "DNSPort is not set in $TOR_CONFIG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if echo "$_TOR_DNS_PORT" | grep -q ":"; then
|
||||
_TOR_DNS_HOST="$(awk -F: '{print $1}' <<< "$_TOR_DNS_PORT")"
|
||||
_TOR_DNS_PORT="$(awk -F: '{print $2}' <<< "$_TOR_DNS_PORT")"
|
||||
fi
|
||||
|
||||
# DNS must be a number
|
||||
if ! [[ "$_TOR_DNS_PORT" =~ ^[0-9]+$ ]]; then
|
||||
log ERROR "DNSPort options is malformed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log NOTICE "Setting up Dnsmasq to use Tor DNS on $_TOR_DNS_HOST:$_TOR_DNS_PORT"
|
||||
|
||||
_IFACE="$(ip route show default | awk '/default/ {print $5}')"
|
||||
|
||||
tee /etc/resolv.conf &> /dev/null << EOF
|
||||
# Generated by WireAdmin; DO NOT EDIT
|
||||
nameserver 127.0.0.1
|
||||
option allow-domains *.onion
|
||||
search .
|
||||
EOF
|
||||
|
||||
tee "$DNSMASQ_CONFIG" &> /dev/null << EOF
|
||||
pid-file=/var/run/dnsmasq.pid
|
||||
interface=$_IFACE
|
||||
user=dnsmasq
|
||||
group=dnsmasq
|
||||
bind-dynamic
|
||||
no-resolv
|
||||
no-poll
|
||||
no-negcache
|
||||
bogus-priv
|
||||
log-queries
|
||||
domain-needed
|
||||
cache-size=1500
|
||||
min-port=4096
|
||||
server=$_TOR_DNS_HOST#$_TOR_DNS_PORT
|
||||
log-facility=/var/log/dnsmasq/dnsmasq.log
|
||||
EOF
|
||||
mkdir -p /var/log/dnsmasq
|
||||
uown dnsmasq /var/log/dnsmasq
|
||||
}
|
15
rootfs/etc/wireadmin/internal/logrotate.sh
Executable file
15
rootfs/etc/wireadmin/internal/logrotate.sh
Executable file
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
setup_logrotate() {
|
||||
tee "/etc/logrotate.d/rotator" &> /dev/null << EOF
|
||||
/var/log/dnsmasq/dnsmasq.log
|
||||
/var/log/wireadmin/*.log {
|
||||
size 512K
|
||||
rotate 3
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 root adm
|
||||
copytruncate
|
||||
}
|
||||
EOF
|
||||
}
|
95
rootfs/etc/wireadmin/internal/tor.sh
Executable file
95
rootfs/etc/wireadmin/internal/tor.sh
Executable file
@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
|
||||
TOR_CONFIG="/etc/tor/torrc"
|
||||
TOR_CONFIG_TEMPLATE="$TOR_CONFIG.template"
|
||||
|
||||
_cleanse_config() {
|
||||
# Remove comment line with single Hash
|
||||
sed -i '/^#\([^#]\)/d' "$TOR_CONFIG"
|
||||
|
||||
# Remove options with no value. (KEY[:space:]{...VALUE})
|
||||
sed -i '/^[^ ]* $/d' "$TOR_CONFIG"
|
||||
|
||||
# Remove duplicate lines
|
||||
sed -i '/^$/N;/\n.*\n/d' "$TOR_CONFIG"
|
||||
|
||||
# Remove double empty lines
|
||||
sed -i '/^$/N;/^\n$/D' "$TOR_CONFIG"
|
||||
}
|
||||
|
||||
_fix_permissions() {
|
||||
mkdir -p /var/lib/tor
|
||||
uown tor /var/lib/tor
|
||||
chmod +x /var/lib/tor
|
||||
}
|
||||
|
||||
_load_from_env() {
|
||||
local added_count=0
|
||||
local updated_count=0
|
||||
for _env_name in $(env | grep -o "^TOR_[^=]*"); do
|
||||
|
||||
# skip custom options
|
||||
if [[ " ${CUSTOM_TOR_OPTIONS[*]} " == *" ${_env_name} "* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local env_value="${!_env_name}"
|
||||
|
||||
# remove prefix and convert to camel case
|
||||
local option=$(to_camel_case "${_env_name#TOR_}")
|
||||
if [ -n "$env_value" ]; then
|
||||
|
||||
# Check if there is a corresponding option in the torrc file, and update it
|
||||
if grep -i -q "^$option" "$TOR_CONFIG"; then
|
||||
sed -i "s/^$option.*/$option $env_value/" "$TOR_CONFIG"
|
||||
updated_count=$((updated_count + 1))
|
||||
else
|
||||
echo "$option $env_value" >> "$TOR_CONFIG"
|
||||
added_count=$((added_count + 1))
|
||||
fi
|
||||
|
||||
fi
|
||||
done
|
||||
|
||||
# Add a blank line at the end of the file
|
||||
echo "" >> "$TOR_CONFIG"
|
||||
|
||||
if [ "$added_count" -gt 0 ] || [ "$updated_count" -gt 0 ]; then
|
||||
echo ""
|
||||
log NOTICE "Added $added_count and updated $updated_count options from environment variables."
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
generate_tor_config() {
|
||||
# Copying the torrc template to the torrc file
|
||||
cp "${TOR_CONFIG_TEMPLATE}" "$TOR_CONFIG"
|
||||
|
||||
# IP address of the container
|
||||
local inet_address="$(hostname -i | awk '{print $1}')"
|
||||
|
||||
sed -i "s/{{INET_ADDRESS}}/$inet_address/g" "$TOR_CONFIG"
|
||||
|
||||
# any other environment variables that start with TOR_ are added to the torrc
|
||||
# file
|
||||
env | grep ^TOR_ | sed -e 's/TOR_//' -e 's/=/ /' | while read -r line; do
|
||||
key=$(echo "$line" | awk '{print $1}')
|
||||
value=$(echo "$line" | awk '{print $2}')
|
||||
key=$(to_camel_case "$key")
|
||||
echo "$key $value" >> "$TOR_CONFIG"
|
||||
done
|
||||
|
||||
# Removing duplicated tor options
|
||||
awk -F= '!a[tolower($1)]++' "$TOR_CONFIG" > "/tmp/$(basename "$TOR_CONFIG")" \
|
||||
&& mv "/tmp/$(basename "$TOR_CONFIG")" "$TOR_CONFIG"
|
||||
|
||||
_load_from_env
|
||||
_cleanse_config
|
||||
_fix_permissions
|
||||
|
||||
log "notice" "Tor configuration file has been generated"
|
||||
}
|
||||
|
||||
get_torrc_option() {
|
||||
grep -i "^$1" "$TOR_CONFIG" | awk '{print $2}'
|
||||
}
|
22
rootfs/etc/wireadmin/xscript.sh
Executable file
22
rootfs/etc/wireadmin/xscript.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
source /etc/wireadmin/internal/dns.sh
|
||||
source /etc/wireadmin/internal/logrotate.sh
|
||||
source /etc/wireadmin/internal/tor.sh
|
||||
|
||||
uppercase() {
|
||||
echo "$1" | tr '[:lower:]' '[:upper:]'
|
||||
}
|
||||
|
||||
log() {
|
||||
echo -e "$(date +"%b %d %H:%M:%S %Z") [$(uppercase "$1")] $2"
|
||||
}
|
||||
|
||||
to_camel_case() {
|
||||
echo "$1" | awk -F_ '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2));}1' OFS=""
|
||||
}
|
||||
|
||||
uown() {
|
||||
_UID="$(id -u "$1")"
|
||||
chown -R "$_UID":"$_UID" "$2"
|
||||
}
|
14
web/src/lib/components/iconset/dnsmasq-icon.svelte
Normal file
14
web/src/lib/components/iconset/dnsmasq-icon.svelte
Normal file
@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import Icon from '@lib/components/iconset/icon.svelte';
|
||||
import type { Props } from '@lib/components/iconset';
|
||||
|
||||
type $$Props = Props;
|
||||
</script>
|
||||
|
||||
<Icon {...$$restProps} viewBox="0 0 56 30" fill="currentColor">
|
||||
<path
|
||||
d="M47.084 5.32128C42.17 2.30228 35.377 0.436279 27.878 0.436279C20.378 0.436279 13.588 2.30228 8.67402 5.32128C3.75902 8.33728 0.718018 12.5053 0.718018 17.1103C0.718018 21.7143 3.75902 25.8823 8.67402 28.8983C13.588 31.9183 20.378 24.6273 27.878 24.6273C35.377 24.6273 42.17 31.9183 47.084 28.8983C51.998 25.8823 55.039 21.7133 55.039 17.1103C55.039 12.5043 51.998 8.33728 47.084 5.32128ZM17.083 17.4883C14.385 17.4883 12.198 16.5663 12.198 15.4273C12.198 14.2873 14.385 13.3643 17.083 13.3643C19.78 13.3643 21.968 14.2883 21.968 15.4273C21.968 16.5663 19.78 17.4883 17.083 17.4883ZM38.584 17.6793C35.898 17.6793 33.723 16.8233 33.723 15.7673C33.723 14.7133 35.899 13.8563 38.584 13.8563C41.269 13.8563 43.447 14.7133 43.447 15.7673C43.447 16.8233 41.269 17.6793 38.584 17.6793Z"
|
||||
fill="#6700AD"
|
||||
aria-label="Dnsmasq"
|
||||
/>
|
||||
</Icon>
|
@ -1,5 +1,6 @@
|
||||
import type { SVGAttributes } from 'svelte/elements';
|
||||
|
||||
import Dnsmasq from './dnsmasq-icon.svelte';
|
||||
import Root from './icon.svelte';
|
||||
import Onion from './onion-icon.svelte';
|
||||
|
||||
@ -13,9 +14,11 @@ interface Props extends SVGAttributes<SVGSVGElement> {
|
||||
|
||||
export {
|
||||
Root,
|
||||
Dnsmasq,
|
||||
Onion,
|
||||
type Props,
|
||||
//
|
||||
Root as Icon,
|
||||
Dnsmasq as DnsmasqIcon,
|
||||
Onion as OnionIcon,
|
||||
};
|
||||
|
@ -5,11 +5,16 @@
|
||||
type $$Props = Props;
|
||||
</script>
|
||||
|
||||
<Icon {...$$restProps} viewBox="0 0 179.51151 205.15602" fill="currentColor">
|
||||
<g transform="translate(-17.934981,-39.932967)">
|
||||
<path
|
||||
d="m 104.88587,47.145484 -1.60279,-2.804867 q -2.00347,-4.407649 -6.811816,-4.407649 -7.212516,0.801391 -8.414602,8.013907 -2.804867,14.024336 -11.620164,24.041719 -8.815297,9.616688 -20.435462,18.431985 0,0 0,0 0,0 0,0 -14.425031,10.818771 -26.045196,25.644501 -11.219469,14.42503 -12.02086,39.26814 0.400696,24.04172 15.226423,40.47023 14.825727,16.02781 37.66536,23.64102 l -4.407648,12.82225 q -2.003477,8.8153 6.010429,12.42156 8.815298,2.00348 12.421555,-6.01043 l 4.808344,-14.42503 q 4.006954,0.80139 8.414602,1.20208 v 10.01739 q 0.801391,8.81529 9.616685,9.61668 8.8153,-0.80139 9.61669,-9.61668 V 225.4549 q 4.40765,-0.40069 8.4146,-1.20208 l 4.80835,14.42503 q 3.60625,8.01391 12.42155,6.01043 8.01391,-3.60626 6.01043,-12.42156 l -4.40765,-12.82225 q 22.83964,-7.61321 37.66536,-23.64102 14.82573,-16.42851 15.22643,-40.47023 -0.8014,-24.84311 -12.02086,-39.26814 -11.62017,-14.82573 -26.0452,-25.644501 0,0 0,0 0,0 0,0 -11.62016,-8.815297 -20.43546,-18.431985 -8.8153,-10.017383 -11.21947,-24.041719 -1.60278,-7.212516 -8.8153,-8.013907 -4.80834,0 -6.81182,4.407649 l -1.60278,2.804867 q -2.80487,3.606258 -5.60973,0 z M 56.802427,114.4623 v -0.4007 q 0.400695,-0.40069 0.80139,-0.80139 0,0 0,0 1.602782,-2.40417 4.407649,-2.80486 1.202086,0 2.404172,0.40069 0.801391,0 2.003477,0.80139 2.404172,1.60278 2.804867,4.40765 0,1.20209 -0.400695,2.40417 0,0.80139 -0.400696,1.20209 v 0.40069 q -0.400695,0.4007 -0.80139,1.20209 -0.400696,1.60278 -2.003477,4.40765 -2.404172,6.01043 -5.209039,15.22642 -2.804868,9.61669 -3.606258,19.63407 -0.400696,10.01738 2.404172,18.03129 1.602781,6.01043 -4.006953,8.4146 -5.609735,1.60278 -8.013907,-3.60626 -4.006953,-11.62016 -3.205563,-23.64102 1.202086,-12.02086 4.006954,-22.03824 3.205562,-10.41808 6.01043,-16.82921 1.202086,-3.20556 2.003476,-4.80834 0.400695,-0.4007 0.400695,-0.80139 0,-0.4007 0.400696,-0.80139 z M 101.6803,89.218493 q 0.4007,-0.801391 0.4007,-1.202086 0.80139,-1.202086 2.00348,-2.003477 2.00347,-1.602781 4.80834,-1.202086 1.20208,0.400696 2.00348,0.801391 1.20208,0.801391 2.00347,2.003477 0.80139,1.202086 1.20209,2.804867 0,0.801391 0,2.003477 -0.4007,0.80139 -0.4007,1.202086 v 0 l -0.40069,0.80139 q 0,0 0,0.400696 -0.4007,0.80139 -0.80139,1.602781 0,0 0,0.400695 -1.20209,2.804867 -2.40418,7.212516 -3.60625,10.01738 -7.21251,25.6445 -4.006954,15.62712 -5.20904,32.05563 -0.801391,16.8292 3.20556,30.05215 1.60278,5.60973 -4.006951,8.0139 -5.609734,1.20209 -8.013906,-4.00695 -4.808344,-16.02781 -3.606258,-34.4598 1.202086,-18.43198 5.209039,-34.86049 3.606258,-16.02781 7.212516,-26.846589 2.003476,-4.808343 2.80487,-8.013906 0.80139,-1.202086 1.20208,-2.003477 v -0.400695 -0.400695 z m 31.65493,14.825727 q 0.4007,-6.01043 6.41113,-6.411125 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0.40069,0 3.60626,0 5.60974,3.205565 l -0.4007,0.40069 v 0 q 0.4007,-0.40069 0.4007,-0.40069 v 0 0 0 0.40069 l 0.40069,0.4007 q 0.4007,0.40069 0.80139,1.60278 1.20209,2.00348 2.80487,5.60973 3.60626,7.61322 8.01391,19.63407 4.40765,12.02086 6.41112,26.0452 2.00348,14.42503 -1.60278,28.04867 -2.40417,5.60974 -8.0139,4.40765 -5.60974,-2.00347 -4.40765,-7.61321 2.80486,-10.81877 1.20208,-23.24033 -1.60278,-12.02086 -5.60973,-23.64102 -4.00695,-11.21947 -7.61321,-18.03129 -1.60278,-3.60626 -2.40417,-5.60974 v 0 q -2.40418,-1.60278 -2.40418,-4.80834 z"
|
||||
aria-label="Onion"
|
||||
/>
|
||||
</g>
|
||||
<Icon {...$$restProps} viewBox="0 0 512 512" fill="currentColor">
|
||||
<path
|
||||
d="M256 502C391.862 502 502 391.862 502 256C502 120.138 391.862 10 256 10C120.138 10 10 120.138 10 256C10 391.862 120.138 502 256 502Z"
|
||||
fill="#F2E4FF"
|
||||
fill-opacity="0.5"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M256.525 465.44V434.407C354.826 434.123 434.421 354.365 434.421 255.993C434.421 157.628 354.826 77.8702 256.525 77.5863V46.5532C371.964 46.8442 465.447 140.49 465.447 255.993C465.447 371.503 371.964 465.156 256.525 465.44ZM256.525 356.82C311.97 356.529 356.849 311.516 356.849 255.993C356.849 200.477 311.97 155.464 256.525 155.173V124.147C329.115 124.43 387.882 183.339 387.882 255.993C387.882 328.654 329.115 387.562 256.525 387.846V356.82ZM256.525 201.719C286.267 202.003 310.303 226.18 310.303 255.993C310.303 285.812 286.267 309.99 256.525 310.274V201.719ZM0 255.993C0 397.384 114.609 512 256 512C397.384 512 512 397.384 512 255.993C512 114.609 397.384 0 256 0C114.609 0 0 114.609 0 255.993Z"
|
||||
fill="#7D4698"
|
||||
/>
|
||||
</Icon>
|
||||
|
@ -6,14 +6,44 @@ import { fsTouch } from '@lib/utils/fs-extra';
|
||||
|
||||
export const SERVICES = <const>{
|
||||
tor: {
|
||||
name: 'Tor',
|
||||
start:
|
||||
'screen -L -Logfile /var/log/wireadmin/tor.log -dmS tor bash -c "screen -S tor -X wrap off; tor -f /etc/tor/torrc"',
|
||||
stop: 'pkill tor',
|
||||
logfile: '/var/log/wireadmin/tor.log',
|
||||
health: async () => {
|
||||
try {
|
||||
const { stdout } = await execa('screen', ['-ls'], { shell: true });
|
||||
return stdout.includes('tor');
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
dnsmasq: {
|
||||
name: 'Dnsmasq',
|
||||
start: 'dnsmasq',
|
||||
stop: 'pkill dnsmasq',
|
||||
logfile: '/var/log/dnsmasq/dnsmasq.log',
|
||||
health: async () => {
|
||||
try {
|
||||
const { stdout } = await execa('ps cax | grep dnsmasq', { shell: true });
|
||||
return stdout.search(/dnsmasq .* dnsmasq$/gm) !== -1;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export type ServiceName = keyof typeof SERVICES;
|
||||
export type Service = (typeof SERVICES)[ServiceName];
|
||||
|
||||
export function getService(service: ServiceName | string | undefined): Service | undefined {
|
||||
if (!!service && service in SERVICES) {
|
||||
return SERVICES[service as ServiceName];
|
||||
}
|
||||
}
|
||||
|
||||
export function restart(serviceName: ServiceName) {
|
||||
const service = SERVICES[serviceName];
|
||||
|
@ -40,6 +40,7 @@ export const DnsSchema = z
|
||||
.regex(IPV4_REGEX, {
|
||||
message: 'DNS must be a valid IPv4 address',
|
||||
})
|
||||
.default('9.9.9.11')
|
||||
.optional();
|
||||
|
||||
export const MtuSchema = z
|
||||
|
@ -9,7 +9,7 @@
|
||||
import CreateServerDialog from './CreateServerDialog.svelte';
|
||||
import { Button } from '@lib/components/ui/button';
|
||||
import { PlusIcon } from 'lucide-svelte';
|
||||
import { OnionIcon } from '@lib/components/iconset';
|
||||
import { DnsmasqIcon, OnionIcon } from '@lib/components/iconset';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
</CardContent>
|
||||
{/if}
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Services</CardTitle>
|
||||
@ -69,7 +70,12 @@
|
||||
<CardContent>
|
||||
<Service name="Tor" slug="tor">
|
||||
<svelte:fragment slot="icon">
|
||||
<OnionIcon class="text-purple-700 text-xl" />
|
||||
<OnionIcon class="h-6 w-6" />
|
||||
</svelte:fragment>
|
||||
</Service>
|
||||
<Service name="Dnsmasq" slug="dnsmasq">
|
||||
<svelte:fragment slot="icon">
|
||||
<DnsmasqIcon class="h-6 w-6" />
|
||||
</svelte:fragment>
|
||||
</Service>
|
||||
</CardContent>
|
||||
|
@ -7,7 +7,7 @@
|
||||
import { Badge } from '@lib/components/ui/badge';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { cn } from '@lib/utils';
|
||||
import { Layers, LayersIcon } from 'lucide-svelte';
|
||||
import { LayersIcon } from 'lucide-svelte';
|
||||
import { OnionIcon } from '@lib/components/iconset';
|
||||
|
||||
export let server: WgServer;
|
||||
@ -29,7 +29,7 @@
|
||||
>
|
||||
<LayersIcon class="text-gray-400 text-xl" />
|
||||
{#if server.tor}
|
||||
<OnionIcon class="absolute bottom-2 right-2 w-4 h-4 text-purple-700" />
|
||||
<OnionIcon class="absolute bottom-2 right-2 w-4 h-4" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
|
||||
<a href={`/${slug}`} title="Manage" class="my-auto">
|
||||
<a href={`/service/${slug}`} title="Manage" class="my-auto">
|
||||
<span class="text-lg font-medium md:text-base hover:text-primary hover:font-medium">
|
||||
{name}
|
||||
</span>
|
||||
@ -45,7 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href={`/${slug}`} title="Manage the Server" class="hidden md:block">
|
||||
<a href={`/service/${slug}`} title="Manage the Server" class="hidden md:block">
|
||||
<Button variant="outline" size="sm">Manage</Button>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -1,11 +1,12 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
import logger from '@lib/logger';
|
||||
import { getServers, WGServer } from '@lib/wireguard';
|
||||
import { errorBox } from '@lib/logger';
|
||||
import { WG_STORE } from '@lib/storage';
|
||||
import { WGServer } from '@lib/wireguard';
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
try {
|
||||
for (const { id } of await getServers()) {
|
||||
for (const { id } of await WG_STORE.listServers()) {
|
||||
const wg = new WGServer(id);
|
||||
const server = await wg.get();
|
||||
const hasInterface = await wg.isUp();
|
||||
@ -20,7 +21,7 @@ export const GET: RequestHandler = async () => {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('APIFailed: HealthCheck:', e);
|
||||
errorBox(e);
|
||||
return new Response('FAILED', { status: 500, headers: { 'Content-Type': 'text/plain' } });
|
||||
}
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { json, type RequestHandler } from '@sveltejs/kit';
|
||||
import { execa } from 'execa';
|
||||
|
||||
export const GET: RequestHandler = async ({ params }) => {
|
||||
try {
|
||||
const { stdout } = await execa('screen', ['-ls'], { shell: true });
|
||||
const isRunning = stdout.includes(params.serviceName!);
|
||||
|
||||
return json({ healthy: isRunning });
|
||||
} catch (e) {
|
||||
return json({ healthy: false });
|
||||
}
|
||||
};
|
19
web/src/routes/api/health/[service]/+server.ts
Normal file
19
web/src/routes/api/health/[service]/+server.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { json, type RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
import { errorBox } from '@lib/logger';
|
||||
import { getService } from '@lib/services';
|
||||
|
||||
export const GET: RequestHandler = async ({ params }) => {
|
||||
try {
|
||||
const service = getService(params.service);
|
||||
if (!service) {
|
||||
return json({ healthy: false });
|
||||
}
|
||||
const healthy = await service.health();
|
||||
|
||||
return json({ healthy });
|
||||
} catch (e) {
|
||||
errorBox(e);
|
||||
return json({ healthy: false });
|
||||
}
|
||||
};
|
53
web/src/routes/service/[slug]/+page.server.ts
Normal file
53
web/src/routes/service/[slug]/+page.server.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { error, type Actions } from '@sveltejs/kit';
|
||||
|
||||
import logger, { errorBox } from '@lib/logger';
|
||||
import { clearLogs, getService, logs, restart, type ServiceName } from '@lib/services';
|
||||
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const service = getService(params.slug);
|
||||
|
||||
if (!service) {
|
||||
logger.error(`Service not found. Service: ${params.slug}`);
|
||||
throw error(404, { message: 'Not found' });
|
||||
}
|
||||
|
||||
return {
|
||||
slug: params.slug,
|
||||
title: service.name,
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
clearLogs: async ({ params }) => {
|
||||
const { slug } = params as { slug: ServiceName };
|
||||
try {
|
||||
await clearLogs(slug);
|
||||
return {};
|
||||
} catch (e) {
|
||||
errorBox(e);
|
||||
throw error(500, 'Unhandled Exception');
|
||||
}
|
||||
},
|
||||
logs: async ({ params }) => {
|
||||
const { slug } = params as { slug: ServiceName };
|
||||
try {
|
||||
return { logs: await logs(slug) };
|
||||
} catch (e) {
|
||||
errorBox(e);
|
||||
throw error(500, 'Unhandled Exception');
|
||||
}
|
||||
},
|
||||
restart: async ({ params }) => {
|
||||
const { slug } = params as { slug: ServiceName };
|
||||
try {
|
||||
const success = restart(slug);
|
||||
|
||||
return { success };
|
||||
} catch (e) {
|
||||
errorBox(e);
|
||||
throw error(500, 'Unhandled Exception');
|
||||
}
|
||||
},
|
||||
};
|
@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@lib/components/ui/card';
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@lib/components/ui/card';
|
||||
import { onMount } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import fetchAction from '@lib/utils/fetch-action';
|
||||
import { Button } from '@lib/components/ui/button';
|
||||
import { CardFooter } from '@lib/components/ui/card';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
import {
|
||||
@ -18,6 +17,8 @@
|
||||
import BasePage from '@lib/components/page/BasePage.svelte';
|
||||
import { Checkbox } from '@lib/components/ui/checkbox';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let logElement: HTMLTextAreaElement;
|
||||
let logs: string | undefined;
|
||||
let autoScroll = true;
|
||||
@ -83,7 +84,6 @@
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
console.log('clearing interval');
|
||||
clearInterval(interval);
|
||||
};
|
||||
});
|
||||
@ -98,7 +98,7 @@
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Tor</BreadcrumbPage>
|
||||
<BreadcrumbPage>{data.title}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
@ -1,36 +0,0 @@
|
||||
import { promises } from 'node:fs';
|
||||
import { error, type Actions } from '@sveltejs/kit';
|
||||
import { execa } from 'execa';
|
||||
|
||||
import logger from '@lib/logger';
|
||||
import { clearLogs, logs, restart, SERVICES, type ServiceName } from '@lib/services';
|
||||
|
||||
export const actions: Actions = {
|
||||
clearLogs: async ({ params }) => {
|
||||
try {
|
||||
await clearLogs('tor');
|
||||
return {};
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
throw error(500, 'Unhandled Exception');
|
||||
}
|
||||
},
|
||||
logs: async ({ params }) => {
|
||||
try {
|
||||
return { logs: await logs('tor') };
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
throw error(500, 'Unhandled Exception');
|
||||
}
|
||||
},
|
||||
restart: async ({ params }) => {
|
||||
try {
|
||||
const success = restart('tor');
|
||||
|
||||
return { success };
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
throw error(500, 'Unhandled Exception');
|
||||
}
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue
Block a user