From 9938860d6b1e90ca5212e38fe7e5dc519bbe6dcb Mon Sep 17 00:00:00 2001 From: wusumac <736139669@qq.com> Date: Wed, 12 Aug 2026 23:16:26 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=9B=BA=E5=8C=96=20NAS=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=9C=8D=E5=8A=A1=E5=AE=89=E8=A3=85=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Codex --- deploy/nas/README.md | 4 ++ deploy/nas/deployment.test.ts | 14 +++++ deploy/nas/install-update-service.sh | 93 ++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100755 deploy/nas/install-update-service.sh diff --git a/deploy/nas/README.md b/deploy/nas/README.md index 264aaf16..3b16d9c8 100644 --- a/deploy/nas/README.md +++ b/deploy/nas/README.md @@ -57,6 +57,10 @@ SSH 或 `sudo` 密码。安装时要完成以下动作: 6. 执行 `systemctl daemon-reload`,再手动启动一次服务,确认拉取、重建和健康检查 成功。服务不会常驻,只有本机发布脚本主动调用时才运行。 +仓库内的 `install-update-service.sh` 会执行上述 NAS 安装步骤。它会先用 `visudo` +校验规则,保留现有 `.env` 的路径和 UID/GID,只把镜像标签改成 `edge`;脚本本身 +不会拉取镜像、启动服务或重建容器。 + 部署 `.env` 不存放账号密码,保留以下字段: ```dotenv diff --git a/deploy/nas/deployment.test.ts b/deploy/nas/deployment.test.ts index 221aa095..315f93e3 100644 --- a/deploy/nas/deployment.test.ts +++ b/deploy/nas/deployment.test.ts @@ -13,6 +13,10 @@ const updateSudoers = readFileSync( "deploy/nas/plainleaf-update.sudoers", "utf8", ); +const updateInstaller = readFileSync( + "deploy/nas/install-update-service.sh", + "utf8", +); test("NAS image allows automatic setup, multi-space, and legacy mode detection", () => { expect(dockerfile).toContain('ENTRYPOINT ["/usr/local/bin/plainleaf"]'); @@ -72,3 +76,13 @@ test("root service only runs the Plainleaf updater when explicitly triggered", ( expect(publisher).not.toContain("StrictHostKeyChecking=no"); expect(publisher).not.toContain("sshpass"); }); + +test("one-time installer validates sudoers and restricts the publishing key", () => { + expect(updateInstaller).toContain( + '/usr/sbin/visudo -cf "${SOURCE_DIR}/plainleaf-update.sudoers"', + ); + expect(updateInstaller).toContain('restrict,command=\\"${FORCED_COMMAND}\\"'); + expect(updateInstaller).toContain("systemctl daemon-reload"); + expect(updateInstaller).not.toContain("systemctl enable"); + expect(updateInstaller).not.toMatch(/^systemctl start/m); +}); diff --git a/deploy/nas/install-update-service.sh b/deploy/nas/install-update-service.sh new file mode 100755 index 00000000..629e3a45 --- /dev/null +++ b/deploy/nas/install-update-service.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +if [[ "$EUID" -ne 0 ]]; then + echo "请使用 sudo 运行此安装脚本。" >&2 + exit 1 +fi + +if [[ "$#" -ne 1 ]]; then + echo "用法:$0 " >&2 + exit 1 +fi + +readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly PUBLIC_KEY_FILE="$1" +readonly NAS_USER="13616066635" +readonly NAS_UID="1001" +readonly NAS_GID="1001" +readonly NAS_HOME="/home/${NAS_USER}" +readonly CURRENT_DEPLOY_DIR="/data_s001/data/udata/real/13616066635/docker/plainleaf" +readonly ROOT_CONFIG_DIR="/etc/plainleaf" +readonly AUTHORIZED_KEYS="${NAS_HOME}/.ssh/authorized_keys" +readonly FORCED_COMMAND='sudo -n /usr/bin/systemctl start plainleaf-update.service' + +for required_file in \ + compose.yaml \ + update-plainleaf.sh \ + plainleaf-update.service \ + plainleaf-update.sudoers; do + if [[ ! -r "${SOURCE_DIR}/${required_file}" ]]; then + echo "缺少安装文件:${SOURCE_DIR}/${required_file}" >&2 + exit 1 + fi +done + +if [[ ! -r "$PUBLIC_KEY_FILE" ]]; then + echo "无法读取 SSH 公钥:${PUBLIC_KEY_FILE}" >&2 + exit 1 +fi + +read -r key_type key_body key_comment < "$PUBLIC_KEY_FILE" +if [[ "$key_type" != "ssh-ed25519" || -z "$key_body" ]]; then + echo "SSH 公钥必须是有效的 Ed25519 公钥。" >&2 + exit 1 +fi + +if [[ ! -r "${CURRENT_DEPLOY_DIR}/.env" ]]; then + echo "找不到现有 Plainleaf 环境配置:${CURRENT_DEPLOY_DIR}/.env" >&2 + exit 1 +fi + +/usr/sbin/visudo -cf "${SOURCE_DIR}/plainleaf-update.sudoers" + +install -d -o root -g root -m 0700 "$ROOT_CONFIG_DIR" +install -o root -g root -m 0600 \ + "${SOURCE_DIR}/compose.yaml" \ + "${ROOT_CONFIG_DIR}/compose.yaml" +install -o root -g root -m 0600 \ + "${CURRENT_DEPLOY_DIR}/.env" \ + "${ROOT_CONFIG_DIR}/plainleaf.env" + +if grep -q '^PLAINLEAF_IMAGE_TAG=' "${ROOT_CONFIG_DIR}/plainleaf.env"; then + sed -i 's/^PLAINLEAF_IMAGE_TAG=.*/PLAINLEAF_IMAGE_TAG=edge/' \ + "${ROOT_CONFIG_DIR}/plainleaf.env" +else + printf '\nPLAINLEAF_IMAGE_TAG=edge\n' >> "${ROOT_CONFIG_DIR}/plainleaf.env" +fi + +install -o root -g root -m 0700 \ + "${SOURCE_DIR}/update-plainleaf.sh" \ + /usr/local/sbin/plainleaf-update +install -o root -g root -m 0644 \ + "${SOURCE_DIR}/plainleaf-update.service" \ + /etc/systemd/system/plainleaf-update.service +install -o root -g root -m 0440 \ + "${SOURCE_DIR}/plainleaf-update.sudoers" \ + /etc/sudoers.d/plainleaf-update + +install -d -o "$NAS_UID" -g "$NAS_GID" -m 0700 "$NAS_HOME" +install -d -o "$NAS_UID" -g "$NAS_GID" -m 0700 "${NAS_HOME}/.ssh" +touch "$AUTHORIZED_KEYS" +chown "${NAS_UID}:${NAS_GID}" "$AUTHORIZED_KEYS" +chmod 0600 "$AUTHORIZED_KEYS" + +authorized_line="restrict,command=\"${FORCED_COMMAND}\" ${key_type} ${key_body} ${key_comment:-plainleaf-nas-publisher}" +if ! grep -Fqx "$authorized_line" "$AUTHORIZED_KEYS"; then + printf '%s\n' "$authorized_line" >> "$AUTHORIZED_KEYS" +fi + +systemctl daemon-reload + +echo "Plainleaf 主动更新服务安装完成。"