归档 NapCat-Docker 镜像(上游仓库与镜像已删除)

This commit is contained in:
TanTan committed 2026-11-12 22:43:19 +08:00
commit a397d63b6e
15 files changed
+685

No files matched your search

Executable
+246
View File
@@ -0,0 +1,246 @@
#!/bin/bash
trap "" SIGPIPE
# 安装 napcat
if [ ! -f "napcat/napcat.mjs" ] || [ ! -f "napcat/config/napcat.json" ]; then
unzip -qo NapCat.Shell.zip -d ./NapCat.Shell || exit 1
mkdir -p napcat/config || exit 1
if [ ! -f "napcat/napcat.mjs" ]; then
for item in NapCat.Shell/*; do
if [ "$item" != "NapCat.Shell/config" ]; then
cp -rf "$item" napcat/ || exit 1
fi
done
fi
if [ ! -f "napcat/config/napcat.json" ]; then
mkdir -p napcat/config
cp -rn NapCat.Shell/config/. napcat/config/ || exit 1
fi
rm -rf ./NapCat.Shell
fi
# 配置 WebUI Token
CONFIG_PATH=/app/napcat/config/webui.json
if [ ! -f "${CONFIG_PATH}" ] && [ -n "${WEBUI_TOKEN}" ]; then
echo "正在配置 WebUI Token..."
jq -n --arg prefix "${WEBUI_PREFIX}" --arg token "${WEBUI_TOKEN}" \
'{host: "0.0.0.0", prefix: $prefix, port: 6099, token: $token, loginRate: 3}' > "${CONFIG_PATH}" || exit 1
fi
# 删除字符串两端的引号
remove_quotes() {
local str="$1"
local first_char="${str:0:1}"
local last_char="${str: -1}"
if [[ ($first_char == '"' && $last_char == '"') || ($first_char == "'" && $last_char == "'") ]]; then
# 两端都是双引号
if [[ $first_char == '"' ]]; then
str="${str:1:-1}"
# 两端都是单引号
else
str="${str:1:-1}"
fi
fi
echo "$str"
}
if [ -n "${MODE}" ] && [ ! -f /app/napcat/config/onebot11.json ]; then
case "${MODE}" in
reverse_ws)
jq --arg url "${ONEBOT_URL:?MODE=reverse_ws requires ONEBOT_URL}" \
'.network.websocketClients[0].url = $url' \
/app/templates/nonebot.json > /app/napcat/config/onebot11.json.tmp || exit 1
;;
reverse_http)
jq --arg url "${ONEBOT_URL:?MODE=reverse_http requires ONEBOT_URL}" \
'.network.httpClients = [.network.websocketClients[0] | .name = "reverse_http" | .url = $url | del(.heartInterval, .reconnectInterval)] | .network.websocketClients = []' \
/app/templates/nonebot.json > /app/napcat/config/onebot11.json.tmp || exit 1
;;
*) cp "/app/templates/${MODE}.json" /app/napcat/config/onebot11.json.tmp || exit 1 ;;
esac
mv /app/napcat/config/onebot11.json.tmp /app/napcat/config/onebot11.json || exit 1
fi
rm -rf "/tmp/.X1-lock"
# 删除容器标识
rm -f "/.dockerenv"
rm -f "/.dockerinit"
rm -f "/run/.containerenv"
rm -f "/run/systemd/container"
rm -f "/dev/.dockerenv"
# Name规范
HOSTNAME_VAL="$(hostname)"
if [[ "${HOSTNAME_VAL}" == *docker* || "${HOSTNAME_VAL}" == *container* || "${HOSTNAME_VAL}" == *lxc* ]] \
|| [[ "${HOSTNAME_VAL}" =~ ^[a-f0-9]{12,}$ ]]; then
hostname "localhost"
echo "localhost" > /etc/hostname
fi
# cgroup处理
mkdir -p /tmp/fake_cgroup
FAKE_CGROUP_DIR="/tmp/fake_cgroup"
# /proc/self/cgroup
# /proc/1/cgroup
for cfile in /proc/self/cgroup /proc/1/cgroup; do
if [ -f "$cfile" ]; then
FAKE_CG="$FAKE_CGROUP_DIR/cgroup_$(basename $(dirname $cfile))_$(basename $cfile)"
cat "$cfile" | sed \
-e 's|/docker/|/system.slice/|g' \
-e 's|/docker-|/system.slice/|g' \
-e 's|/lxc/|/system.slice/|g' \
-e 's|/podmaster|/system.slice/|g' \
-e 's|/podman/|/system.slice/|g' \
-e 's|/kubepods/|/system.slice/|g' \
-e 's|/containerd/|/system.slice/|g' \
-e 's|/buildkit/|/system.slice/|g' \
> "$FAKE_CG"
mount --bind "$FAKE_CG" "$cfile" 2>/dev/null || true
fi
done
# DMI
for dmi_file in /sys/class/dmi/id/product_name /sys/class/dmi/id/product_uuid /sys/class/dmi/id/sys_vendor /sys/class/dmi/id/board_vendor /sys/class/dmi/id/board_name; do
if [ -f "$dmi_file" ]; then
FAKE_DMI="/tmp/fake_cgroup/$(echo $dmi_file | tr '/' '_')"
case "$(basename $dmi_file)" in
product_name) echo "Standard PC" > "$FAKE_DMI" ;;
product_uuid) echo "00000000-0000-0000-0000-000000000000" > "$FAKE_DMI" ;;
sys_vendor|board_vendor) echo "Intel Corporation" > "$FAKE_DMI" ;;
board_name) echo "Default string" > "$FAKE_DMI" ;;
esac
mount --bind "$FAKE_DMI" "$dmi_file" 2>/dev/null || true
fi
done
# Docker Socket
if [ -S /var/run/docker.sock ]; then
mv /var/run/docker.sock /var/run/.docker.sock.hidden 2>/dev/null || true
fi
# Mount Info
for mfile in /proc/self/mountinfo /proc/1/mountinfo; do
if [ -f "$mfile" ]; then
FAKE_MOUNT="$FAKE_CGROUP_DIR/mountinfo_$(basename $(dirname $mfile))_$(basename $mfile)"
cat "$mfile" | sed \
-e 's|overlay / |ext4 / |g' \
-e 's|overlay /|ext4 /|g' \
-e '/docker/d' \
-e '/containerd/d' \
-e '/\.dockerenv/d' \
-e '/cgroup2.*docker/d' \
> "$FAKE_MOUNT"
mount --bind "$FAKE_MOUNT" "$mfile" 2>/dev/null || true
fi
done
# /proc/1/cmdline
if [ -f /proc/1/cmdline ]; then
FAKE_CMDLINE="$FAKE_CGROUP_DIR/cmdline_1"
printf '/sbin/init\0' > "$FAKE_CMDLINE"
mount --bind "$FAKE_CMDLINE" /proc/1/cmdline 2>/dev/null || true
fi
# /proc/1/sched
if [ -f /proc/1/sched ]; then
FAKE_SCHED="$FAKE_CGROUP_DIR/sched_1"
head -1 /proc/1/sched | sed 's|([^)]*)|(1, #threads=1)|' > "$FAKE_SCHED"
tail -n +2 /proc/1/sched >> "$FAKE_SCHED"
sed -i 's/^[a-zA-Z0-9_-]*\s/systemd /' "$FAKE_SCHED"
mount --bind "$FAKE_SCHED" /proc/1/sched 2>/dev/null || true
fi
# /proc/1/environ
if [ -f /proc/1/environ ]; then
FAKE_ENVIRON="$FAKE_CGROUP_DIR/environ_1"
cat /proc/1/environ | tr '\0' '\n' | \
grep -v -i 'container=docker\|container=lxc\|container=podman\|container=containerd' | \
tr '\n' '\0' > "$FAKE_ENVIRON"
mount --bind "$FAKE_ENVIRON" /proc/1/environ 2>/dev/null || true
fi
# /proc/1/status & /proc/self/status (NSpid)
for sfile in /proc/1/status /proc/self/status; do
if [ -f "$sfile" ]; then
FAKE_STATUS="$FAKE_CGROUP_DIR/status_$(basename $(dirname $sfile))_$(basename $sfile)"
cat "$sfile" | sed 's/^NSpid:.*/NSpid:\t1/' > "$FAKE_STATUS"
mount --bind "$FAKE_STATUS" "$sfile" 2>/dev/null || true
fi
done
# /proc/1/stat
if [ -f /proc/1/stat ]; then
FAKE_STAT="$FAKE_CGROUP_DIR/stat_1"
cat /proc/1/stat | sed 's/^\([0-9]*\) ([-a-zA-Z0-9_]*)/\1 (systemd)/' > "$FAKE_STAT"
mount --bind "$FAKE_STAT" /proc/1/stat 2>/dev/null || true
fi
# /etc/hosts
if [ -f /etc/hosts ]; then
FAKE_HOSTS="$FAKE_CGROUP_DIR/hosts"
cat /etc/hosts | grep -v "$(hostname)" > "$FAKE_HOSTS" 2>/dev/null
grep -q '^127\.0\.0\.1.*localhost' "$FAKE_HOSTS" || echo "127.0.0.1\tlocalhost" >> "$FAKE_HOSTS"
grep -q '^::1.*localhost' "$FAKE_HOSTS" || echo "::1\tlocalhost ip6-localhost ip6-loopback" >> "$FAKE_HOSTS"
mount --bind "$FAKE_HOSTS" /etc/hosts 2>/dev/null || true
fi
# /sys/hypervisor/type
if [ -f /sys/hypervisor/type ]; then
FAKE_HYPER="$FAKE_CGROUP_DIR/hypervisor_type"
echo "" > "$FAKE_HYPER"
mount --bind "$FAKE_HYPER" /sys/hypervisor/type 2>/dev/null || true
fi
# /proc/self/mounts
if [ -f /proc/self/mounts ]; then
FAKE_MOUNTS="$FAKE_CGROUP_DIR/mounts_self"
cat /proc/self/mounts | sed \
-e 's|overlay / |ext4 / |g' \
-e 's|overlay /|ext4 /|g' \
-e '/docker/d' \
-e '/containerd/d' \
> "$FAKE_MOUNTS"
mount --bind "$FAKE_MOUNTS" /proc/self/mounts 2>/dev/null || true
fi
# /proc/self/cpuset
if [ -f /proc/self/cpuset ]; then
FAKE_CPUSET="$FAKE_CGROUP_DIR/cpuset_self"
cat /proc/self/cpuset | sed \
-e 's|/docker/|/|g' \
-e 's|/docker-[^/]*/|/|g' \
-e 's|/kubepods/|/|g' \
-e 's|/containerd/|/|g' \
> "$FAKE_CPUSET"
mount --bind "$FAKE_CPUSET" /proc/self/cpuset 2>/dev/null || true
fi
: ${NAPCAT_GID:=0}
: ${NAPCAT_UID:=0}
usermod -o -u ${NAPCAT_UID} napcat
groupmod -o -g ${NAPCAT_GID} napcat
usermod -g ${NAPCAT_GID} napcat
chown -R ${NAPCAT_UID}:${NAPCAT_GID} /app
# 关闭 core dump。QQ 会预留上 TB 的虚拟地址空间,崩溃时 core 能写出几百 GB,
# Docker Desktop (WSL) 下会直接写满宿主机系统盘(NapCatQQ #2060)。
# WSL 这类管道形式的 core_pattern 不受 ulimit 限制,所以同时把 coredump_filter 清零,
# 崩溃时只留寄存器等基本信息。需要完整 core 调试时设置 NAPCAT_ENABLE_COREDUMP=1。
if [ "${NAPCAT_ENABLE_COREDUMP}" != "1" ]; then
ulimit -c 0 2>/dev/null || true
echo 0 > /proc/self/coredump_filter 2>/dev/null || true
fi
gosu napcat Xvfb :1 -screen 0 1080x760x16 +extension GLX +render > /dev/null 2>&1 &
sleep 2
export DISPLAY=:1
cd /app/napcat
if [ -n "${ACCOUNT}" ]; then
gosu napcat /opt/QQ/qq --no-sandbox -q $ACCOUNT
else
gosu napcat /opt/QQ/qq --no-sandbox
fi