【一键脚本】Komari WebSSH 安全提示 MOTD 持久禁用脚本

miao7 发布于 2026-09-18 212 次阅读


先说句题外话。

我其实个人觉得 Komari 作者做得挺好的,完全没有觉得越权。作者的出发点确实是善意的 —— 有了这个提示,你确实能第一时间发现「某台不该装 Komari 的机器被入侵了」这件事。文案看起来也是深思熟虑过的,包括不给关闭选项,我猜也是为了这个「快速发现入侵」的考虑,而不是那种「教用户做事」的味道。

而且 WebSSH 都开了,Agent 顺手改个 MOTD 提示「你已经开启远程控制」,我个人是觉得无伤大雅的 🙂

但是 —— 看多了还是不舒服。所以只能用一种不怎么优雅的方式解决了 🙂

一、脚本原理

Komari-Agent 每次重启都会重新写入一遍 MOTD 警告块。基于这个行为,脚本的思路是:

  1. 监测 /etc/motd 的变动 —— 一旦变动,立刻把 Komari 添加的那一段删掉
  2. 每天兜底检测一次 —— 防止某些文件系统上 inotify 看不到变化

这样既不用改 Agent,也不用关掉 WebSSH,面板的远程控制能力完全保留,只是 MOTD 里那几行字会被自动清掉。

关于 set -- --persist 那一行

脚本顶部有一行 set -- --persist,它让脚本默认就走持久模式。

模式 怎么用 效果
持久模式(默认) 直接跑,或保留 set -- --persist 清除 + 持续监听 + 每天兜底。Agent 重启后也会被自动清掉
单次模式 删掉 set -- --persist 这一行 只清除一次,不监听。Agent 重启后 MOTD 里那一段又会冒出来

建议直接在 Komari 主控批量运行 —— 主控有批量执行能力,一次把所有节点都处理掉,省得一台台登录。

二、脚本特性

精确删除 只删 Komari 那一段。服务商自己的 banner、其它内容一个字都不动;文件的 inode、权限、属主、属组全部保留(原地重写,不重建文件)
自动备份 动任何东西之前先写一份带时间戳的备份到 /var/backups/komari-motd/
边界严格 块边界完全对齐 Agent 自己的代码:从 [Komari] ... 那行到 Uninstall Komari Agent: ... 那行,再加它后面空的一行。两个块或残缺块 → 拒绝执行,什么都不改
附带清理 如果 /etc/update-motd.d/ 下有 Komari 留下的钩子(且带 Komari 标记),一并删掉;不是 Komari 管理的则跳过
不碰 Agent 全程不会启动、停止、重配或卸载 Komari Agent,面板远程控制能力完好
可视化结果 结束时输出一行机器可读的 [result] host=... status=... rc=...,方便主控批量收集
兼容性好 POSIX sh,无 bashism,ASCII 输出。只用 grep / awk / sed / cut / cp / rm / date / basename / head —— Debian (dash + coreutils) 和 Alpine (busybox) 都自带

三、脚本源码

#!/bin/sh
# ---------------------------------------------------------------------------
# remove-komari-motd.sh -- strip the Komari Agent security warning from /etc/motd
#
# Why: current Komari Agent builds append a warning block to /etc/motd while the
# agent runs (upstream: cmd/warn_linux.go -> installMOTDWarning):
#
#   [Komari] Remote control is enabled on this device
#   <panel host> can execute commands and read or modify files on this device as root.
#   If you did not set this up, your device may have been accessed without authorization.
#   Stop Komari Agent immediately and check your device for signs of compromise.
#
#   Uninstall Komari Agent: https://komari-document.pages.dev/en/faq/uninstall
#
# What this script does:
#   1. removes exactly that block from /etc/motd. A provider banner or any other
#      content is left untouched, and the file keeps its inode, mode, owner and
#      group (content is rewritten in place, not recreated);
#   2. removes a leftover Komari hook under /etc/update-motd.d/ only if that file
#      carries the Komari marker;
#   3. writes a timestamped backup before touching anything;
#   4. verifies the result and prints one machine-readable [result] line.
#
# The block boundaries follow the agent's own code: from the "[Komari] ..." line
# through the "Uninstall Komari Agent: ..." line, plus one following empty line
# (the agent inserts one). If what is left is an empty file, the file is removed,
# exactly like the agent does on shutdown. Two blocks or a truncated block =>
# refuse and change nothing (the agent refuses those too).
#
# IMPORTANT: the agent re-appends the warning every time it (re)starts, i.e. on
# reboot, on crash-restart and after its self-update. A one-shot run is therefore
# not permanent. Two supported ways to make it permanent:
#   * ./remove-komari-motd.sh --persist
#       installs a tiny guard that re-strips automatically. On systemd it is
#       event driven: a .path unit watches /etc/motd with inotify and reacts
#       within a fraction of a second of the agent's rewrite (a boot + daily
#       timer stays as a safety net for filesystems where inotify cannot see it).
#       Where systemd is absent: a boot hook plus a 15-minute periodic hook.
#       Nothing else is changed, the panel keeps remote control.
#   * add --disable-web-ssh to the agent command line / unit ExecStart
#       official Komari switch: the agent then removes the warning itself and
#       never writes it again -- but the panel also loses remote control
#       (web ssh + RCE) for that node.
#
# Compatibility: POSIX sh, no bashisms, ASCII-only output. Uses only grep, awk,
# sed, cut, cp, rm, date, basename, head -- all present on Debian (dash +
# coreutils) and Alpine (busybox). It never starts, stops, reconfigures or
# uninstalls the Komari agent.
#
# Exit codes: 0 = removed or nothing to do, 1 = error, 2 = refused to modify,
#             3 = not root, 4 = usage error
# ---------------------------------------------------------------------------
set -- --persist
set -u

PROG=${0##*/}
SELF=$0

MOTD_PATH=${KOMARI_MOTD_PATH:-/etc/motd}
BACKUP_DIR=${KOMARI_MOTD_BACKUP_DIR:-/var/backups/komari-motd}
GUARD_DIR=/usr/local/lib/komari-motd
GUARD_SH=$GUARD_DIR/guard.sh
UNIT_NAME=komari-motd-guard
SERVICE_FILE=/etc/systemd/system/$UNIT_NAME.service
PATH_FILE=/etc/systemd/system/$UNIT_NAME.path
TIMER_FILE=/etc/systemd/system/$UNIT_NAME.timer
LOCAL_HOOK=/etc/local.d/$UNIT_NAME.start
PERIODIC_HOOK=/etc/periodic/15min/$UNIT_NAME
LEGACY_DIR=/etc/update-motd.d

START_MARKER='[Komari] Remote control is enabled on this device'
END_PREFIX='Uninstall Komari Agent: '
LEGACY_MARKER='# Komari Agent managed MOTD warning'

DRY=0
PERSIST=0
UNPERSIST=0
GUARD=0
VERBOSE=1

info() { printf '[info] %s\n' "$*"; }
ok()   { printf '[ ok ] %s\n' "$*"; }
warn() { printf '[warn] %s\n' "$*"; }
fail() { printf '[fail] %s\n' "$*"; }
vex()  { if [ "$VERBOSE" = 1 ]; then ok "$*"; fi; return 0; }
vinfo(){ if [ "$VERBOSE" = 1 ]; then info "$*"; fi; return 0; }
vwarn(){ if [ "$VERBOSE" = 1 ]; then warn "$*"; fi; return 0; }

have() { command -v "$1" >/dev/null 2>&1; }

usage() {
	cat <<EOF
$PROG -- remove the Komari Agent security-warning block from /etc/motd

Usage: $PROG [options]

Options:
  -n, --dry-run     show what would be removed, change nothing
  -p, --persist     strip now and install a guard that keeps /etc/motd clean.
                    systemd: event driven (.path unit on /etc/motd) + hourly
                    safety timer. otherwise: boot hook + 15-minute hook.
  -u, --unpersist   remove the guard installed by --persist
  -h, --help        this help

  --motd PATH       operate on PATH instead of /etc/motd (testing; implies no
                    root requirement and is never combined with --persist)
  --guard           internal quiet mode used by the installed guard

Environment:
  KOMARI_MOTD_PATH        same as --motd
  KOMARI_MOTD_BACKUP_DIR  backup directory, default $BACKUP_DIR

Exit codes: 0 ok / nothing to do, 1 error, 2 refused, 3 not root, 4 usage error
EOF
}

# --------------------------------------------------------------------------
# option parsing
# --------------------------------------------------------------------------
while [ $# -gt 0 ]; do
	case $1 in
		-n|--dry-run)   DRY=1 ;;
		-p|--persist)   PERSIST=1 ;;
		-u|--unpersist) UNPERSIST=1 ;;
		--guard)        GUARD=1; VERBOSE=0 ;;
		--motd)         shift; [ $# -gt 0 ] || { fail "--motd needs an argument"; exit 4; }; MOTD_PATH=$1 ;;
		--motd=*)       MOTD_PATH=${1#--motd=} ;;
		-h|--help)      usage; exit 0 ;;
		*)              fail "unknown option: $1"; usage; exit 4 ;;
	esac
	shift
done

if [ "$MOTD_PATH" = "/etc/motd" ] && [ "$DRY" != 1 ] && [ "$(id -u)" != 0 ]; then
	fail "must run as root (use: sudo $PROG)"
	exit 3
fi
if [ "$PERSIST" = 1 ] && [ "$MOTD_PATH" != "/etc/motd" ]; then
	fail "--persist only works on the real /etc/motd"
	exit 4
fi

# --------------------------------------------------------------------------
# environment reporting
# --------------------------------------------------------------------------
os_name() {
	if [ -r /etc/os-release ]; then
		( . /etc/os-release 2>/dev/null; printf '%s' "${PRETTY_NAME:-${ID:-unknown}}" )
	else
		printf 'unknown'
	fi
}

init_system() {
	if [ -d /run/systemd/system ] && have systemctl; then
		printf 'systemd'
	elif have rc-service || [ -x /sbin/openrc-run ]; then
		printf 'openrc'
	elif [ -d /etc/periodic ]; then
		printf 'cron/periodic'
	else
		printf 'unknown'
	fi
}

agent_state() {
	st=not-found
	if [ -f /opt/komari/agent ] || [ -f /etc/systemd/system/komari-agent.service ] || [ -f /etc/init.d/komari-agent ]; then
		st=installed
	fi
	if [ "$st" = installed ]; then
		if have pgrep && pgrep -f 'komari' >/dev/null 2>&1; then
			st="$st,running"
		elif ps 2>/dev/null | grep -v grep | grep -q 'komari'; then
			st="$st,running"
		else
			st="$st,not-running"
		fi
	fi
	printf '%s' "$st"
}

agent_disables_web_ssh() {
	for f in /etc/systemd/system/komari-agent.service /etc/init.d/komari-agent /opt/komari/komari-agent.service; do
		[ -f "$f" ] || continue
		if grep -q -- '--disable-web-ssh' "$f" 2>/dev/null; then
			return 0
		fi
	done
	return 1
}

# --------------------------------------------------------------------------
# backup helper: backup_file <path> ; prints the backup path or nothing
# --------------------------------------------------------------------------
backup_file() {
	_bf_src=$1
	_bf_dir=$BACKUP_DIR
	if ! mkdir -p "$_bf_dir" 2>/dev/null; then
		vwarn "cannot create backup dir $_bf_dir -- continuing without a backup"
		return 0
	fi
	_bf_dst=$_bf_dir/$(basename "$_bf_src").$(date +%Y%m%d-%H%M%S).$$
	if cp -p "$_bf_src" "$_bf_dst" 2>/dev/null; then
		printf '%s' "$_bf_dst"
	else
		vwarn "cannot copy $_bf_src to $_bf_dir -- continuing without a backup"
	fi
	return 0
}

# --------------------------------------------------------------------------
# strip_motd: the actual removal
# returns 0 removed, 10 nothing to do, 2 refused, 1 error
# --------------------------------------------------------------------------
strip_motd() {
	sf=$MOTD_PATH

	if [ -L "$sf" ]; then
		tgt=$(readlink -f "$sf" 2>/dev/null || true)
		if [ -n "$tgt" ]; then
			vinfo "$sf is a symlink to $tgt"
			sf=$tgt
		fi
	fi

	if [ ! -e "$sf" ]; then
		vinfo "$sf does not exist -- nothing to strip"
		return 10
	fi
	if [ ! -f "$sf" ]; then
		fail "$sf is not a regular file -- refusing to touch it"
		return 2
	fi
	if ! grep -qF "$START_MARKER" "$sf" 2>/dev/null; then
		vinfo "no Komari warning block in $sf -- nothing to strip"
		return 10
	fi

	cnt=$(grep -cF "$START_MARKER" "$sf" 2>/dev/null || true)
	if [ "${cnt:-0}" -gt 1 ] 2>/dev/null; then
		fail "$sf holds $cnt Komari warning blocks -- refusing to modify (the agent refuses this too)"
		return 2
	fi

	start=$(grep -nF "$START_MARKER" "$sf" | head -n 1 | cut -d: -f1)
	end=$(awk -v s="$start" -v p="$END_PREFIX" 'NR >= s && index($0, p) == 1 { print NR; exit }' "$sf")
	if [ -z "$end" ]; then
		fail "$sf holds an incomplete Komari warning (no line starting with '$END_PREFIX') -- refusing to modify"
		return 2
	fi

	removed=$((end - start + 1))
	size_before=$(wc -c < "$sf" | tr -d ' ')

	if [ "$DRY" = 1 ]; then
		vex "dry-run: would remove $removed line(s) (lines $start-$end) from $sf, $size_before bytes now"
		return 0
	fi

	if [ "$GUARD" != 1 ]; then
		bk=$(backup_file "$sf")
		if [ -n "$bk" ]; then
			vex "backup: $bk"
		fi
	fi

	tmp=$sf.komari-strip.$$
	if ! awk -v s="$start" -v e="$end" 'NR < s || NR > e { if (NR == e + 1 && $0 == "") next; print }' "$sf" > "$tmp"; then
		rm -f "$tmp"
		fail "awk failed while rewriting $sf -- nothing changed"
		return 1
	fi

	# rewrite content in place: the inode, mode, owner, group and ACLs survive
	if ! cat "$tmp" > "$sf" 2>/dev/null; then
		rm -f "$tmp"
		fail "cannot write $sf -- nothing changed"
		return 1
	fi
	rm -f "$tmp"

	if grep -qF "$START_MARKER" "$sf" 2>/dev/null; then
		fail "verification failed: the marker is still in $sf"
		return 1
	fi

	if [ ! -s "$sf" ]; then
		vex "removed $removed line(s) (lines $start-$end) from $sf; nothing else was in it, removing the empty file"
		rm -f "$sf"
		return 0
	fi

	vex "removed $removed line(s) (lines $start-$end) from $sf; no other content touched, mode/owner kept"
	return 0
}

# --------------------------------------------------------------------------
# leftover /etc/update-motd.d hook
# --------------------------------------------------------------------------
strip_legacy_hooks() {
	_lh_seen=0
	for h in "$LEGACY_DIR"/*komari*; do
		[ -f "$h" ] || continue
		_lh_seen=1
		first=$(head -n 1 "$h" 2>/dev/null)
		if [ "$first" != "$LEGACY_MARKER" ]; then
			vwarn "$h exists but is not Komari-managed -- leaving it alone"
			continue
		fi
		if [ "$DRY" = 1 ]; then
			vex "dry-run: would delete the Komari hook $h"
			continue
		fi
		bk=$(backup_file "$h")
		if [ -n "$bk" ]; then
			vex "backup: $bk"
		fi
		if rm -f "$h"; then
			vex "removed leftover Komari hook $h"
		else
			warn "cannot remove $h"
		fi
	done
	[ "$_lh_seen" = 1 ] || vinfo "no Komari hook in $LEGACY_DIR"
	return 0
}

# --------------------------------------------------------------------------
# guard: keeps /etc/motd clean without touching the agent
# --------------------------------------------------------------------------
install_guard() {
	if [ "$(id -u)" != 0 ]; then
		fail "must run as root to install the guard"
		return 1
	fi

	if ! mkdir -p "$GUARD_DIR"; then
		fail "cannot create $GUARD_DIR"
		return 1
	fi

	sed -e "s|@@MOTD@@|$MOTD_PATH|g" \
	    -e "s|@@START@@|$START_MARKER|g" \
	    -e "s|@@END@@|$END_PREFIX|g" > "$GUARD_SH" <<'GUARD_EOF'
#!/bin/sh
# Generated by remove-komari-motd.sh --persist. Re-strips the Komari Agent
# warning from the MOTD after the agent (re)started. Silent, always exit 0.
M='@@MOTD@@'
S='@@START@@'
E='@@END@@'
[ -f "$M" ] || exit 0
grep -qF "$S" "$M" 2>/dev/null || exit 0
c=$(grep -cF "$S" "$M" 2>/dev/null)
[ "${c:-0}" -gt 1 ] 2>/dev/null && exit 0
n=$(grep -nF "$S" "$M" | head -n 1 | cut -d: -f1)
[ -n "$n" ] || exit 0
e=$(awk -v s="$n" -v p="$E" 'NR >= s && index($0, p) == 1 { print NR; exit }' "$M")
[ -n "$e" ] || exit 0
t="$M.komari-guard.$$"
if awk -v s="$n" -v e="$e" 'NR < s || NR > e { if (NR == e + 1 && $0 == "") next; print }' "$M" > "$t"; then
	if cat "$t" > "$M"; then
		rm -f "$t"
		[ -s "$M" ] || rm -f "$M"
	fi
else
	rm -f "$t"
fi
exit 0
GUARD_EOF

	if [ ! -s "$GUARD_SH" ]; then
		fail "cannot write $GUARD_SH"
		return 1
	fi
	chmod 0755 "$GUARD_SH" 2>/dev/null
	ok "guard script: $GUARD_SH"

	if [ -d /run/systemd/system ] && have systemctl; then
		cat > "$SERVICE_FILE" <<EOF
[Unit]
Description=Remove the Komari Agent MOTD security warning
Documentation=file:$GUARD_SH
After=komari-agent.service

[Service]
Type=oneshot
ExecStart=$GUARD_SH
# the guard prints nothing when there is nothing to do; keep systemd's own
# start/finish chatter out of the journal too
LogLevelMax=notice
EOF
		cat > "$PATH_FILE" <<EOF
[Unit]
Description=Watch $MOTD_PATH for the Komari Agent security warning

[Path]
PathModified=$MOTD_PATH
Unit=$UNIT_NAME.service

[Install]
WantedBy=paths.target
EOF
		cat > "$TIMER_FILE" <<EOF
[Unit]
Description=Safety-net sweep for the Komari Agent MOTD security warning

[Timer]
OnBootSec=1min
OnUnitActiveSec=1d
AccuracySec=1min
Unit=$UNIT_NAME.service

[Install]
WantedBy=timers.target
EOF
		if systemctl daemon-reload 2>/dev/null &&
			systemctl enable --now "$UNIT_NAME.path" >/dev/null 2>&1 &&
			systemctl enable --now "$UNIT_NAME.timer" >/dev/null 2>&1; then
			ok "event watch: $UNIT_NAME.path (PathModified=$MOTD_PATH) -- reacts in well under a second, no polling"
			ok "safety net:  $UNIT_NAME.timer (at boot + daily), for filesystems where inotify sees nothing"
		else
			warn "wrote the units but could not enable them; run:"
			warn "  systemctl daemon-reload && systemctl enable --now $UNIT_NAME.path $UNIT_NAME.timer"
		fi
		return 0
	fi

	# no systemd: boot hook (Alpine local.d) + periodic hook (busybox crond)
	_guard_installed=0
	info "no systemd here, so no inotify .path unit: boot hook + 15-minute poll instead"
	if [ -d /etc/local.d ]; then
		printf '#!/bin/sh\n%s\n' "$GUARD_SH" > "$LOCAL_HOOK" && chmod 0755 "$LOCAL_HOOK" 2>/dev/null && _guard_installed=1
		ok "boot hook: $LOCAL_HOOK"
		if have rc-update; then
			if rc-update show default 2>/dev/null | grep -q '^[[:space:]]*local'; then
				ok "openrc service 'local' is already enabled"
			else
				warn "enable it once with: rc-update add local default"
			fi
		fi
	fi
	if [ -d /etc/periodic/15min ]; then
		printf '#!/bin/sh\nexec %s\n' "$GUARD_SH" > "$PERIODIC_HOOK" && chmod 0755 "$PERIODIC_HOOK" 2>/dev/null && _guard_installed=1
		ok "periodic hook: $PERIODIC_HOOK"
		if have rc-service; then
			if rc-service crond status >/dev/null 2>&1; then
				ok "crond is running, the periodic hook will fire"
			else
				warn "crond is not running; enable it once with: rc-update add crond default && rc-service crond start"
			fi
		fi
	fi
	if [ "$_guard_installed" = 0 ]; then
		warn "no systemd and no /etc/local.d or /etc/periodic -- guard not installed"
		warn "fallback: put '$GUARD_SH' into whatever cron your init system runs"
		return 1
	fi
	return 0
}

remove_guard() {
	_g_removed=0
	if [ -f "$TIMER_FILE" ] || [ -f "$SERVICE_FILE" ] || [ -f "$PATH_FILE" ]; then
		if have systemctl; then
			systemctl disable --now "$UNIT_NAME.timer" "$UNIT_NAME.path" >/dev/null 2>&1
		fi
		rm -f "$TIMER_FILE" "$SERVICE_FILE" "$PATH_FILE"
		have systemctl && systemctl daemon-reload >/dev/null 2>&1
		ok "removed systemd guard units (path + timer + service)"
		_g_removed=1
	fi
	for f in "$LOCAL_HOOK" "$PERIODIC_HOOK"; do
		if [ -f "$f" ]; then
			rm -f "$f" && ok "removed $f"
			_g_removed=1
		fi
	done
	if [ -d "$GUARD_DIR" ]; then
		rm -rf "$GUARD_DIR" && ok "removed $GUARD_DIR"
		_g_removed=1
	fi
	[ "$_g_removed" = 1 ] || info "no guard installed, nothing to remove"
	return 0
}

# --------------------------------------------------------------------------
# main
# --------------------------------------------------------------------------
if [ "$UNPERSIST" = 1 ]; then
	remove_guard
	_rc=$?
	_un_host=$(hostname 2>/dev/null || printf 'unknown')
	printf '[result] host=%s status=unpersisted rc=%s\n' "$_un_host" "$_rc"
	exit "$_rc"
fi

if [ "$GUARD" = 1 ]; then
	# silent periodic run
	strip_motd
	rc=$?
	strip_legacy_hooks
	if [ "$rc" = 0 ]; then exit 0; fi
	exit 0
fi

HOSTNAME_STR=$(hostname 2>/dev/null || printf 'unknown')
info "host=$HOSTNAME_STR os=$(os_name) init=$(init_system)"
info "komari agent: $(agent_state)"

if [ "$PERSIST" = 1 ] && [ "$(id -u)" != 0 ]; then
	fail "must run as root to install the guard"
	exit 3
fi

strip_motd
STRIP_RC=$?
strip_legacy_hooks

case $STRIP_RC in
	0)  STATUS=removed ;;
	10) STATUS=clean ;;
	2)  STATUS=refused ;;
	*)  STATUS=error ;;
esac

if [ "$STRIP_RC" = 10 ]; then
	vex "nothing to do: $MOTD_PATH is already free of the Komari warning"
fi

if [ "$DRY" = 1 ]; then
	printf '[result] host=%s status=dry-run rc=%s\n' "$HOSTNAME_STR" "$STRIP_RC"
	exit 0
fi

if [ "$STRIP_RC" = 0 ] || [ "$STRIP_RC" = 10 ]; then
	if [ "$(agent_state)" = installed,running ] || [ "$(agent_state)" = installed,not-running ]; then
		if agent_disables_web_ssh; then
			ok "the agent runs with --disable-web-ssh, so it removes the warning itself and will not write it again"
		elif [ "$PERSIST" != 1 ]; then
			warn "the agent re-adds this warning on every (re)start; run with --persist for a permanent result"
		fi
	fi
fi

if [ "$PERSIST" = 1 ] && [ "$STRIP_RC" != 2 ]; then
	install_guard
	ok "guard armed: the warning is removed again automatically"
fi

printf '[result] host=%s status=%s rc=%s\n' "$HOSTNAME_STR" "$STATUS" "$STRIP_RC"

case $STRIP_RC in
	0|10) exit 0 ;;
	2)    exit 2 ;;
	*)    exit 1 ;; 
esac                               

四、参数说明

参数 说明
-n, --dry-run 只显示「将会删除什么」,不做任何修改
-p, --persist 立即清除 + 安装守护,保持长期干净。systemd 环境下用 .path 单元事件驱动(inotify 监听,亚秒级响应),另配一个开机 + 每天的定时器兜底;没有 systemd 时退化成正向启动钩子 + 15 分钟轮询
-u, --unpersist 卸载 --persist 装的守护,恢复原状
-h, --help 查看帮助
--motd PATH 对指定文件操作而不是 /etc/motd(测试用,此时不需要 root,且不能和 --persist 一起用)
--guard 内部静默模式,供已安装的守护调用

环境变量

变量 默认值 说明
KOMARI_MOTD_PATH /etc/motd 等同于 --motd
KOMARI_MOTD_BACKUP_DIR /var/backups/komari-motd 备份目录

退出码

码 含义
0 已删除 / 无需处理
1 出错
2 拒绝修改(检测到异常情况)
3 非 root 运行
4 参数使用错误

五、快速使用

1. 保存脚本

sudo nano /root/remove-komari-motd.sh
# 粘贴上面的脚本内容,保存退出
sudo chmod +x /root/remove-komari-motd.sh

2. 先干跑看看会删什么

sudo /root/remove-komari-motd.sh --dry-run

3. 正式执行(持久模式)

sudo /root/remove-komari-motd.sh
# 脚本顶部已有 set -- --persist,直接跑就是持久模式

4. 验证结果

# MOTD 里应该已经找不到 Komari 字样
grep -i komari /etc/motd

# 守护单元应该都在
systemctl status komari-motd-guard.path
systemctl status komari-motd-guard.timer

# 备份目录
ls -la /var/backups/komari-motd/

5. 想撤销怎么办

# 卸载守护(MOTD 保持当前状态,Agent 下次重启会重新写入)
sudo /root/remove-komari-motd.sh --unpersist

# 如果想彻底恢复,从备份还原
sudo cp -p /var/backups/komari-motd/motd.* /etc/motd

六、官方开关(更优雅,但有代价)

其实 Komari 官方提供了一个开关,比这个脚本优雅得多:

# 在 Agent 的启动命令 / systemd 单元的 ExecStart 里加上:
--disable-web-ssh

效果:Agent 会自己移除那段警告,并且以后再也不写。

代价:该节点的面板远程控制能力也没了(WebSSH + RCE)。

所以这是个取舍:

  • 要保留远程控制 → 用本文脚本,MOTD 干净,能力保留
  • 不需要远程控制 → 直接用官方 --disable-web-ssh,最干净

七、一点想法

写这个脚本的过程里我想明白了几个事,也顺便记一下:

  1. 这个提示本身是有价值的。它把一个「静默的、高权限的远程控制通道」变得可见 —— 这是很多监控 Agent 都不做的事,作者愿意做,值得肯定。
  2. 「看得见」和「每天都要看见」是两件事。第一次知道很重要,第一百次就是噪音了。
  3. 官方不给关闭选项,我理解但不完全认同。安全设计里「不可关闭」确实能防住「用户随手关掉然后忘了」,但也把「我知道风险、我要自己承担」这类用户逼到了写脚本的路上 —— 比如我 🙂
  4. 所以本文不是「绕过安全提示」教程,而是一个「我已经知情并接受风险,只是想让终端干净点」的方案。脚本全程不碰 Agent、不降权限、不改任何安全配置。

提醒:如果你不知道为什么 MOTD 里会出现 Komari 提示,那说明这台机器可能被未经授权地装了 Agent —— 请立刻停掉 Agent 并检查机器。这个脚本是给「自己知道、自己装的」人用的,不是给「发现异常后装作没看见」的人用的。


免责声明:本文仅为个人使用体验与技术记录。脚本不修改 Komari Agent 本体,不改变任何权限或安全配置,仅清理 /etc/motd 中的特定文本块。请自行评估风险,遵守相关法律法规。