# @mingo_789/nas-deploy

> Build compressed Docker images and deploy single-container applications to a NAS over SSH.

Latest version **0.1.0** (published 2026-09-24) · UNLICENSED license · 0 weekly downloads

## Install

```sh
npm install @mingo_789/nas-deploy
pnpm add @mingo_789/nas-deploy
yarn add @mingo_789/nas-deploy
bun add @mingo_789/nas-deploy
```

Provides the command `nas-deploy`.

## Health

**Score 60/100 (C)** — status: active.

Positive: esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.1.0 |
| Published | 2026-09-24 |
| First published | 2026-09-24 |
| Weekly downloads | 0 |
| License | UNLICENSED |
| TypeScript types | none |
| Module format | ESM |
| Node | >=22 |
| Dependencies | 0 |
| Unpacked size | 88.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | mingo_789 |
| Keywords | nas, synology, docker, deploy, ssh, cli |

## Links

- npm: https://www.npmjs.com/package/@mingo_789/nas-deploy
- npm.io page: https://npm.io/package/@mingo_789/nas-deploy

## Alternatives

- [@salesforce/cli](https://npm.io/package/@salesforce/cli.md) — 389.7K weekly downloads
- [@mintlify/cli](https://npm.io/package/@mintlify/cli.md) — 208.9K weekly downloads
- [@grafana/e2e-selectors](https://npm.io/package/@grafana/e2e-selectors.md) — 128.7K weekly downloads
- [mintlify](https://npm.io/package/mintlify.md) — 112.0K weekly downloads
- [@intlayer/cli](https://npm.io/package/@intlayer/cli.md) — 22.8K weekly downloads

## Recent versions

- 0.1.0 (latest) — 2026-09-24

## README

# nas-deploy

把「本机构建 Docker 镜像 → 压缩 → SSH/SCP 上传 → NAS 加载 → 健康检查 → 清理」封装成一个 npm 命令行包。适合个人 NAS 上的单容器应用，应用仍维护自己的 Dockerfile。

包名为 **`@mingo_789/nas-deploy`**。零第三方运行时依赖，使用 Node.js 内置模块、本机 Docker/OpenSSH 和 NAS 上的 Bash/Docker。本文的 `0.1.0` 是首版安装示例，实际版本以 package.json 和维护者发布结果为准；完成发包准备不等于已发布 npm。

## 文档入口

- 使用者：从下方安装和接入教程开始，日常操作见「命令和恢复」。
- 配置参考：[使用案例](docs/使用案例.md)，说明端口映射、host 网络、健康检查和应用变量如何从项目实际要求转成配置。
- AI 接入：先读 [AGENT_GUIDE.md](AGENT_GUIDE.md)，按需读取配置、部署和迁移教程，所有页面都随包发布。
- 维护者：[npm 发布教程](docs/npm发布.md)，包含升版、预演、正式发包、预发布标签和失败重试。

注意命令所在项目：**本工具源码仓库的 `npm run release` 是发 npm 包；应用中的 `nas-deploy release` 才是部署到 NAS。**

## 默认行为

- 每次构建生成独立镜像标签，例如 `my-app:20260924093000-a1b2c3d4e5f6`，不覆盖 `latest`。
- `docker save` 的输出流经过 gzip 压缩，以 `.partial` 写入；进程和压缩流全部成功后，才原子重命名为 `.tar.gz`。
- 记录镜像 ID、平台、文件大小、SHA-256；部署前本地校验，上传后 NAS 再校验。
- 上传镜像和应用环境文件也使用 `.partial`，校验后再改为正式文件名。
- 先加载并验证镜像、创建候选容器，再停止旧容器。候选容器健康后，才替换正式名称并清理。
- **默认仅保留当前成功版本。** 删除旧容器、未使用的本项目镜像和本次本地/NAS 压缩包。NAS 临时运行环境文件也会删除；Docker 已在创建容器时读取环境变量。
- 本地旧的本项目镜像也会清理，保留本次部署镜像，以及任何仍被容器使用的镜像。
- 不执行全局 `image prune`、`system prune` 或 `builder prune`；不删除数据目录、卷、其他项目镜像、手动添加的镜像标签或构建缓存。
- 发布失败保留恢复容器、归档和诊断状态，**不自动回滚共享数据库**。可显式运行 `rollback` 恢复旧容器。
- 本地和 NAS 两端都有发布锁；错误配置、无健康检查、平台不匹配、其他工具创建的同名容器会被拒绝。

这是停服替换流程，会有短暂中断，不提供零停机切流、多容器编排或镜像仓库推送。

## 环境准备

本机：Node.js 22+、Docker CLI 和正在运行的 Docker 服务、SSH/SCP。gzip 压缩由 Node.js 完成，无需 Python。跨架构构建能力由本机 Docker 提供。

NAS：Bash 3+、`sha256sum`、支持健康检查和 `--mount` 的 Docker/Container Manager。用户需要 Docker 权限，或可通过 `sudo -n` 调用 Docker。工具自动尝试群晖 Container Manager 和旧 Docker 套件的路径。

SSH 使用密钥或 SSH agent，要求目标已在 `known_hosts` 中。首次使用先通过普通 SSH 登录核对主机指纹。工具启用 `BatchMode` 和严格主机校验，不接收 SSH 密码、不自动接受主机指纹，也不提供交互 sudo。默认使用 `scp -O` 兼容没有 SFTP 子系统的 NAS。

## 安装

包正式发布后，在需要部署的应用项目内安装固定版本：

```bash
npm install --save-dev --save-exact @mingo_789/nas-deploy@0.1.0
# pnpm 项目：
pnpm add -D -E @mingo_789/nas-deploy@0.1.0
```

若该版本尚未发布，使用维护者提供的 `.tgz`，或按下面步骤从源码生成，不需要全局安装。

### 从本地源码打包和安装

```bash
cd /path/to/nas-deploy
npm ci
npm run pack:check
```

`pack:check` 执行语法检查、全部测试，并在仓库外离线安装真实归档，验证 CLI、通用初始化、dry-run 和随包文档。产物为 `artifacts/packages/mingo_789-nas-deploy-0.1.0.tgz`，报告在 `artifacts/pack-check.json`。归档文件名随版本变化。普通 `npm pack` 也保留可用，会检查并测试，但不执行隔离消费验收。

在使用方项目中安装本地产物并固定版本：

```bash
npm install --save-dev --save-exact /path/to/nas-deploy/artifacts/packages/mingo_789-nas-deploy-0.1.0.tgz
# pnpm 项目：
pnpm add -D -E /path/to/nas-deploy/artifacts/packages/mingo_789-nas-deploy-0.1.0.tgz
```

以上是本机验证方式，锁文件会记录本地压缩包路径。跨机器/CI 使用前，应分发这个固定版本的压缩包，或发布到可访问的 npm registry，再改成包版本依赖；不要在团队锁文件中保留个人电脑的绝对路径。

## 新项目接入

以下命令都在**应用项目根目录**执行。应用应已有可用 Dockerfile；先确认 NAS 架构、应用端口、数据目录和健康端点。

每个项目只需初始化一次，编辑并保存项目自己的配置。后续发布复用这份配置，不用再次运行 init。

### 1. 初始化配置

```bash
npx --no-install nas-deploy init
cp .env.deploy.example .env.deploy
```

### 2. 填写连接与应用配置

编辑 `nas-deploy.config.json`，将默认 3000 端口、数据挂载和平台改为应用实际值，再填写 `.env.deploy`：

```dotenv
NAS_HOST=192.168.1.194
NAS_USER=nas
# NAS_SSH_PORT=22
# NAS_IDENTITY_FILE=/absolute/path/to/private-key
```

Dockerfile 有 HEALTHCHECK 时可直接使用；没有时，在 JSON 中配置容器内执行的 `health.command`。例如应用运行于 Node.js 且提供 `/health`：

```json
{
  "health": {
    "command": "node -e \"fetch('http://127.0.0.1:3000/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))\"",
    "timeout": 120
  }
}
```

将该字段合入配置，而不是用它替换整个文件。命令中的端口应为容器内部端口，检查程序必须已安装在镜像内。应用没有该端点时，应按实际情况提供健康检查。

### 3. 添加应用脚本

`init` 不覆盖已有配置，并向 `.gitignore` 和 `.dockerignore` 添加必要排除规则。它不会自动修改应用的 `package.json`；添加以下命令即可：

```json
{
  "scripts": {
    "build:image": "nas-deploy build",
    "deploy": "nas-deploy deploy",
    "release": "nas-deploy release"
  }
}
```

```bash
npx --no-install nas-deploy release --dry-run
npx --no-install nas-deploy doctor
npm run release
npx --no-install nas-deploy status
```

上面依次预览配置、检查连接和 Docker、发布、查看状态。pnpm 项目用 `pnpm exec nas-deploy ...` 和 `pnpm run release`。第一次成功后，再发布一次可验证旧镜像与归档的自动清理。

`--dry-run` 只读取本地配置并展示计划，不连接 NAS、不执行 Docker、不写文件，也不展示应用环境变量值。真实 release 会短暂停止旧容器；有不兼容数据迁移时，先按应用要求备份。

### 4. 日常更新和工具升级

应用代码更新后直接执行 `npm run release`；要先构建再择时部署，用 `npm run build:image` 后再 `npm run deploy`。默认成功后压缩包会删除，下次更新应重新构建。

升级公共工具时，在应用中安装经过验证的新版本并更新锁文件，例如 `npm install -D -E @mingo_789/nas-deploy@<新版本>`；阅读新版本 CHANGELOG，先 dry-run 再做一次实际发布验证。部署工具修复不需要再同步多份 Shell 脚本。

如果只做接入配置，到 dry-run 为止即可；尚未完成真实 NAS 发布时，不应报告部署成功。

## 配置案例教程

工具只生成通用初始配置，项目差异由配置文件表达。[使用案例](docs/使用案例.md) 参考两个项目，给出完整配置及推导步骤：

- plot-assistant：端口映射、镜像自带健康检查、采集令牌和持久化目录。
- blood-games：host 网络、额外健康检查、SQLite 路径和模型环境变量。

案例是阅读参考，不是 CLI 内置能力。结合自己的项目调整一次并保存配置即可。

### 从旧脚本迁移

旧的 `.env.deploy` 是 Bash 配置；新工具使用字面量环境文件，**不会执行 `source`、变量引用或命令替换**。把 `REMOTE_HOST` 改为 `NAS_HOST`，`REMOTE_USER` 改为 `NAS_USER`，把网络、端口、挂载和 Docker 设置迁到 JSON。`REMOTE_PASS` 不再使用，`REMOTE_DOCKER='sudo docker'` 对应 `remote.sudo: "always"`。

首次接管已有部署需要安排维护窗口：

1. 先备份持久化数据，确认新配置挂载的是原来的数据目录。
2. 执行 `build` 和 `doctor`，确认配置、镜像和连接可用。
3. 在 NAS 停止原容器，将其改名为另一个明确的备份名称，例如 `plot-assistant-legacy`，释放原名称和端口。
4. 使用新工具执行 `deploy`，验证应用和已有数据。
5. 确认成功后，手动清理旧工具创建的备份容器和镜像。

新工具不修改、删除或接管缺少本项目管理标签的同名容器；旧备份也不在自动清理范围内。首次迁移失败时通过旧容器手动恢复，后续由本工具发布的版本才支持工具内的失败恢复。

## 配置

所有路径以配置文件所在目录为基准，和执行命令时的目录无关。配置文件使用严格 JSON，未知字段会报错。

```json
{
  "project": "my-app",
  "image": "my-app",
  "platform": "linux/amd64",
  "build": {
    "context": ".",
    "dockerfile": "Dockerfile",
    "args": { "PUBLIC_BASE": "/" }
  },
  "remote": {
    "directory": "/volume1/docker/my-app",
    "port": 22,
    "sudo": "auto",
    "legacyScp": true,
    "connectTimeout": 10
  },
  "container": {
    "name": "my-app",
    "network": "bridge",
    "ports": [{ "address": "0.0.0.0", "host": 3000, "container": 3000, "protocol": "tcp" }],
    "mounts": [{ "source": "/volume1/docker/my-app/data", "target": "/app/data", "owner": "1000:1000" }],
    "env": { "NODE_ENV": "production", "PORT": "3000" },
    "envFrom": ["APP_TOKEN"],
    "restart": "unless-stopped"
  },
  "health": {
    "timeout": 120
  },
  "cleanup": {
    "retainPrevious": 0,
    "localArchive": true,
    "localImages": true
  },
  "artifactDir": ".nas-deploy"
}
```

| 字段 | 说明 |
| --- | --- |
| `project` | 必填，小写项目标识，用于资源所有权标签；同一 NAS 上不同应用应使用不同标识 |
| `image` | 镜像仓库名，默认 `project`，版本标签由工具生成；不支持带端口的 registry 名称 |
| `platform` | `linux/amd64`（默认）或 `linux/arm64` |
| `build` | context、Dockerfile、非敏感构建参数 `args`、可选阶段 `target` |
| `remote.host/user` | 可写入 JSON，也可通过 `NAS_HOST/NAS_USER` 提供 |
| `remote.directory` | 项目专属工作目录，默认 `/volume1/docker/<project>`；`NAS_DIRECTORY` 可覆盖 |
| `remote.docker` | 可选 Docker 可执行文件绝对路径，不是 Shell 命令 |
| `remote.sudo` | `auto`、`never` 或 `always`，只使用免交互 `sudo -n` |
| `remote.identityFile` | 可选密钥路径；支持 `NAS_IDENTITY_FILE` 覆盖 |
| `remote.port` | SSH/SCP 端口；支持 `NAS_SSH_PORT` 覆盖 |
| `container.network` | `bridge` 或 `host`，host 模式不能同时配置 ports |
| `container.ports` | host/container 端口，支持 IPv4 绑定地址和 tcp/udp |
| `container.mounts` | 目录 bind mount；可选 `readOnly` 和 `owner`，二者不能同时使用 |
| `container.env` | 非敏感固定环境变量，值必须是字符串 |
| `container.envFrom` | 从环境文件/进程环境中提取的变量名白名单，发布时未配置会报错；可显式设为空。构建、状态和恢复不要求这些变量齐全 |
| `container.user` | 可选数值 `UID` 或 `UID:GID`，默认沿用镜像 |
| `container.command` | 可选字符串数组，覆盖镜像 CMD |
| `health.command` | 可选容器内 Shell 检查命令；不配置时要求镜像有 HEALTHCHECK |
| `health.timeout` | 整体等待健康的秒数，默认 120 |
| `health.interval/checkTimeout/startPeriod/retries` | 配置 command 时生效，默认 5/3/20 秒、6 次 |
| `cleanup.retainPrevious` | 默认 0：只留当前；可设 1：保留一个 previous 容器及其镜像 |
| `cleanup.localArchive` | 默认 true：部署成功后删除本次本地压缩包和对应 manifest |
| `cleanup.localImages` | 默认 true：清理未被使用的本项目旧版本本地镜像；不清理构建缓存 |
| `envFile` | 环境文件路径，默认 `.env.deploy`；显式指定的文件必须存在 |
| `artifactDir` | 本机构建产物及锁目录，默认 `.nas-deploy` |

`mount.owner` 会在旧容器停止后、启动新容器前，使用新镜像的 `chown` 程序递归设置所有权。因此仅应用于项目专属目录，且镜像需要有 `chown`；不指定则不改权限。只读挂载必须预先准备合适权限。

对于 health command，可参考 [使用案例](docs/使用案例.md) 中的 Node.js `fetch` 检查，也可使用镜像内已有的其他工具；命令会在容器内部执行。健康检查必须有意义，仅容器进程启动不算发布成功。

## 环境变量与敏感信息

应用变量优先级：**进程环境 > `.env.deploy` > `container.env`**，其中只有 `envFrom` 显式列出的名字会从外部读取。NAS 连接变量优先级：进程环境 > 环境文件 > JSON。

环境文件支持 `KEY=value`、可选 `export` 前缀、整行注释、空值及单/双引号。引号内内容原样使用，不处理反斜线转义、`$VARIABLE`、`${VARIABLE}` 或 `$(command)`；每个值必须是单行。未加引号的值中，空白后面的 `#` 开始注释。

```dotenv
APP_TOKEN='literal-value-with-$-characters'
OPENAI_API_KEY=
```

应用环境文件只包含白名单变量，使用临时目录和 0600 权限，通过 SCP 传递，不拼接到 SSH 命令参数中。部署日志不主动打印应用日志或变量值。Docker 管理员仍可通过容器配置查看运行环境，这不是独立密钥管理系统。

构建时保留项目已有的 Dockerfile 专属 `.dockerignore` 或根 `.dockerignore` 规则，使用临时 Dockerfile 追加排除 `.git`、`.env*`、本工具产物目录和当前环境文件，避免把这些内容带入构建上下文。原项目文件不被改写。不要通过 `build.args` 传递密钥；本版本不封装 BuildKit secrets。

## 命令和恢复

```bash
nas-deploy build                         # 只构建，不连接 NAS
nas-deploy deploy                        # 部署最新的本地完整产物
nas-deploy deploy --release <release-id>  # 部署仍保留的指定产物
nas-deploy release                       # 构建并部署
nas-deploy status                        # 查看当前、候选和恢复容器
nas-deploy rollback                      # 显式恢复保留的旧容器
nas-deploy cleanup                       # 重试健康部署后的清理
nas-deploy doctor                        # 检查本机 Docker、NAS Docker/架构/sha256sum
nas-deploy release --config ./deploy/nas.json --env-file ./production.env
```

正常健康发布后，NAS 目录只保留归属标记、`last-success` 发布记录及应用持久化目录。本地 `.nas-deploy/last-deployed.json` 记录最后一次部署产物，帮助重试本地清理。`build` 单独运行时保留产物，失败的本地构建/发布产物不自动删除。

如果 NAS 已经健康，但后续清理失败，命令返回非零。日志会指出已进入清理阶段；先 `status` 核实，再运行 `cleanup` 重试，不必重新构建。若清理在 NAS 阶段失败，本地尚未写入 `last-deployed.json`，本次本地归档会保留，之后可手动删除。

失败的候选容器使用 `<name>-candidate`，旧容器一般仍为 `<name>`；晋升中断时旧容器可能名为 `<name>-previous`。`rollback` 会识别这些情况，先启动旧版本并等待健康，再删除候选容器和无用文件。首次部署没有旧容器可恢复时会明确报错，需检查 NAS 容器日志并手动删除候选容器后重试。

默认成功发布会删除旧版本，所以**成功后没有历史版本可供 rollback**。需要保留恢复点时，事先设 `cleanup.retainPrevious: 1`。容器恢复不恢复数据库、文件或权限变更；不兼容的数据迁移需停止应用并恢复部署前备份后，再启动旧版本。

强制终止或断电可能留下 `.nas-deploy/.lock` 或 NAS 的 `<remote.directory>/.deploy-lock`。先确认没有本机命令、SSH 会话或 NAS 部署进程仍在执行，再手动移除对应锁；工具不会按时间自动抢占锁。普通失败会尝试释放本次操作持有的锁，绝不会删除其他操作的锁。

## 验证范围

`npm test` 使用临时目录、独立的本地/NAS Docker 状态以及 SSH/SCP 替身，执行实际 CLI 和远端 Bash 脚本。覆盖压缩包写入失败、校验失败、上传失败、容器创建失败、健康失败、晋升中断、恢复、并发锁、跨项目清理隔离、保留策略和参数转义。测试不访问真实 NAS，也不要求 Docker 服务运行。

实机验收需要运行 Docker 服务，在测试 NAS 或独立测试目录执行 `doctor` 和连续两次 `release`，确认真实健康检查和清理结果后，再接入生产项目。

---
_Source: https://npm.io/package/@mingo_789/nas-deploy · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
