本文面向开发与设计维护;安装、部署和认证操作分别见 使用与运维和 Authelia 认证。
nsetup 是 Linux 主机上的 Docker Compose 应用管理工具,面向家庭服务器场景:
一台机器、一个反向代理入口、若干容器化应用。
单个 musl 静态链接二进制包含两种角色:
- daemon:以 root 运行的 systemd 服务,独占 Docker 操作权限;
- CLI:普通用户使用的管理命令,通过本机 Unix socket 上的 gRPC 调用 daemon。
nihility用户组成员即可操作,无需sudo。
flowchart LR
CLI["nsetup CLI"] -- "gRPC over Unix socket<br/>(可选 TCP + token 远程)" --> D["nsetup daemon"]
D --> T["template<br/>内置模板"]
D --> S["spec<br/>统一数据结构 IR"]
D --> O["orchestrator<br/>项目编排"]
T --> S --> O
O --> K["docker compose"] --> Docker[(Docker)]
O --> FS[("stacks_root/<br/>项目目录")]
整个系统只有一种「应用运行所需数据」的表示(IR)。任何输入(TOML 配置、CLI
标志、compose 文件)都先转换为 IR,daemon 只从 IR 生成 compose.yaml;修改时
从 compose.yaml + .env 解析回 IR,改完重新生成。compose 文件是项目状态的
唯一持久化真相。
classDiagram
class StackSpec {
+String name
+Document document
+BTreeMap~String,String~ environment
+load(dir)$
+compose_yaml() String
+env_file() String
}
class Document {
+BTreeMap~String,Service~ services
+BTreeMap~String,Network~ networks
}
class Service {
+String image
+Option~String~ container_name
+Vec~String~ command
+Option~String~ restart
+Option~String~ network_mode
+Vec~String~ networks
+Vec~String~ ports
+Vec~String~ volumes
+BTreeMap~String,String~ environment
+Vec~String~ env_file
+Vec~String~ labels
+Option~String~ user
+Vec~String~ group_add
+Option~Healthcheck~ healthcheck
+Option~Logging~ logging
}
StackSpec --> Document
Document --> Service
class SemanticView["语义视图(由字段双向派生)"] {
+routes() Vec~Route~
+set_routes(routes, middlewares)
+user_route_identities() Vec~RouteIdentity~
+route_bindings() Vec~RouteBinding~
+image_version() / set_image_version()
+host_ports() Vec~PublishedPort~
+project_hooks() / service_hooks(stage)
}
Service ..> SemanticView : Traefik labels / image 字段解析
Document/Service是 compose YAML 的强类型模型,serde 双向(序列化 + 解析),全部deny_unknown_fields:不在上表中的 compose 指令(如depends_on、build、顶层volumes、list 形式的 environment/labels)不会 进入 IR。nsetup import在解析前先按白名单剔除这些键并在结果中列出被忽略的 字段,因此接管既有 Compose 项目不会因为无关字段整体失败;受支持字段本身无效 时仍然报错。- 路由冲突判定使用
host + path_prefix + entrypoint + protocol组合 (RouteIdentity),因此同一 host 下可以按路径或协议拆分多条路由。手写labels中的 router 只提取Host(...)身份用于诊断,不参与冲突校验;traefik.enable原样保留,仅在声明了路由时才由 nsetup 补齐。 - 启动钩子(
[services.*.hooks])不属于 compose 字段,序列化到.env的NSETUP_APP_HOOKS_JSON中,由 daemon 在 compose up 前后以项目目录为工作目录 执行。 - 语义视图不独立存储:路由、中间件、镜像版本由 labels 和 image 字段按需解析; 修改时清除旧的生成 labels 再重新生成,派生数据与字段永远一致。
.env解析为environment映射。模板附属文件(Traefik 中间件定义、站点 内容)不属于 IR,只在应用配置时写盘,编辑不触碰。
TOML 是唯一的声明式配置格式,用 template 字段选择内置模板,缺省 app。
nsetup template <名称> 输出带注释的配置骨架。
nsetup template app 输出逐字段注释骨架:必填项与常用最小路由保持启用,具有宿主机
副作用或通常可沿用默认行为的字段以注释示例展示;取消注释前应按实际应用修改。
format = 1
name = "media"
[services.web]
image = "ghcr.io/example/media" # 镜像仓库,不含标签
version = "1.0" # 镜像版本标签(独立配置,升级即改此项)
port = 8080 # 容器端口,供路由与发布使用
publish = ["12780:8080/tcp"] # 宿主机端口映射
volumes = ["/srv/media:/data"] # 绝对路径 bind mount
environment = { LOG_LEVEL = "info" }
command = ["--serve"]
network = "bridge" # bridge | host | external
external_network = "my-net" # network = "external" 时使用
labels = ["com.example.team=infra"]
[services.web.healthcheck]
command = "curl -fsS http://localhost:8080/health"
interval = "30s"
timeout = "3s"
retries = 3
[services.web.traefik] # 反向代理路由(可多个 host)
hosts = ["media", "media.example.com"]
middlewares = ["gzip", "internal-only"]
protocol = "http" # http | https | h2c
sticky_cookie = false
priority = 100
[services.worker]
image = "ghcr.io/example/worker"
version = "1.2"多个子路由必须使用具名映射,而不是顺序数组:
[services.web.traefik.routes.api]
hosts = ["api"]
port = 8080
[services.web.traefik.routes.admin]
hosts = ["admin"]
port = 9090路由键(api / admin)是稳定身份,分别生成
nsetup-<项目>-<服务>-api / nsetup-<项目>-<服务>-admin router 与 backend;
域名、端口和其他路由参数都在同一个具名表内声明,增删或重排其他路由不会改变身份。
旧的 [[services.*.traefik.routes]] 数组语法不再接受。
image 与 version 在生成时合成 image:version 写入 IR,解析时拆开;image
本身带标签或摘要属于配置错误。各模板的版本字段语义一致(其中基础设施模板的
version 分别表示所属模板的容器镜像版本),版本校验(C1)
统一作用于该字段。
服务只要有路由,就同时接入两个网络:外部共享的 nsetup-proxy(Traefik 回源)
和项目网络 project。项目网络的 Docker 名被钉为 <项目>_default,与未显式声明
networks 的服务所处的隐式默认网络同名,因此同一项目里带路由与不带路由的服务
仍在同一个网络上。服务接入多个网络时额外生成
traefik.docker.network=nsetup-proxy,避免 Traefik 选到项目网络而无法回源。
format = 1
template = "traefik"
domain = "example.com"
acme_email = "admin@example.com"
cloudflare_token = "..."
version = "v3.8.0"
http_port = 80
https_port = 443
dashboard_authelia = true生成 Traefik 项目:ACME + Cloudflare DNS 证书、HTTP→HTTPS 重定向、HTTP/3、
仅内网可访问的 dashboard,以及供所有应用引用的中间件配置(authelia / gzip /
forwarded-headers / internal-only / tls)。Traefik 项目与应用项目完全同构:
同样经 IR 生成、受同样的约束、用同样的命令运维。升级 Traefik 就是修改
version 后重新 up --force。
dashboard_authelia = true 会在固定的 internal-only@file 之后追加
authelia@file,使控制台同时受内网来源限制与 Authelia 登录保护;关闭或省略时
只保留内网限制。启用前应先准备可用的 Authelia 配置。
dashboard 只生成一条 router(nsetup-traefik-traefik-dashboard):后端固定是内置的
api@internal(永远不写 loadbalancer.server.port),entrypoint 为 https,
priority 1000,带内网来源限制与主域名通配符证书域名。0.2.1 曾经额外生成一条把容器
8080 当后端的应用式路由,两条 router 抢同一个 Host(),实际命中的是回源到容器端口
的那条(表现为 dashboard 404);0.2.2 起不再生成。
metrics = true 时除启动参数外,还会在 config/dynamic/nsetup.yml 里为指标入口
生成两条 file provider router:Path(/metrics) → prometheus@internal,以及
PathPrefix(/api) || PathPrefix(/debug) → api@internal。没有 router 时该入口
一律 404,Prometheus 的 traefik:8081 job 与 nsetup doctor 都无法工作。
内置 tls 是响应头中间件(HSTS:stsSeconds + stsIncludeSubdomains),不是路由级
TLS 开关;路由是否终止 TLS 由 entrypoint 决定。它必须继续生成,因为历史配置把 tls
写在 middlewares 列表里,缺失会让整条路由报 middleware "tls@file" does not exist。
Traefik 的运行默认值以 main 分支既有基础设施生成器为基线:dashboard 路由固定
指向 api@internal;路由使用 cloudflare resolver 和主域名 + 通配符 SAN;关闭
匿名统计、版本检查、access log 与 tracing;默认启用 Prometheus 指标、ping 健康
检查、HTTP/3、EC256、指定 DNS resolver、30 秒 DNS 传播等待,以及 json-file 日志
滚动。健康检查固定为 CMD ["traefik", "healthcheck", "--ping"]:Docker 的
HEALTHCHECK 是独立进程,读不到容器 command 里的 --ping=true,缺 --ping 会一直报
「please enable ping to use health check」而恒为 unhealthy。当前架构只调整受管路径、
网络名和新增的 Authelia 中间件,不重新猜测基础设施默认值。
format = 1
template = "authelia"
host = "auth"
version = "4.39.20"
default_redirection_url = "https://example.com"
default_policy = "one_factor"
jwt_secret = "...至少 32 字符..."
session_secret = "...至少 32 字符..."
storage_encryption_key = "...至少 32 字符..."
[users.admin]
display_name = "Administrator"
password_hash = '$argon2id$...'
email = "admin@example.com"
groups = ["admins"]OIDC provider 是 Authelia 模板的可选全局能力,只拥有 provider HMAC 与签名 私钥:
[oidc]
hmac_secret = "...至少 64 个 RFC3986 非保留字符..."
jwk_private_key = """
-----BEGIN PRIVATE KEY-----
...PKCS#8 或 PKCS#1 RSA 私钥...
-----END PRIVATE KEY-----
"""
# 可选:把 ID Token 之外的 claim 直接写入 ID Token。
# 键名即客户端 claims_policy 引用的策略名。
[oidc.claims_policies.default]
id_token = ["groups", "email", "email_verified", "preferred_username", "name"]Authelia 默认只把协议必需的 claim 放进 ID Token,其余 claim 按规范通过
UserInfo 端点交付。Grafana 等客户端不会主动请求 UserInfo,因此需要
claims_policies 这一逃生舱把 email、name、groups、preferred_username
写进 ID Token,否则登录可用但邮箱、显示名与角色映射全部为空。每个 policy 至少
声明 id_token 或 access_token 之一;policy 属于 provider,由客户端用
claims_policy = "<名称>" 引用,引用了未声明的策略时 Authelia 启动即报错。
客户端不属于 Authelia TOML,而是由对应的 app TOML 声明。具名映射键是稳定的
client_id:
[authelia.oidc_clients.gitea]
client_name = "Gitea"
client_secret_hash = '$pbkdf2-sha512$...'
authorization_policy = "two_factor"
redirect_uris = ["https://git.example.com/user/oauth2/authelia/callback"]
scopes = ["openid", "profile", "email", "groups"]
grant_types = ["authorization_code", "refresh_token"]
require_pkce = false
token_endpoint_auth_method = "client_secret_basic"
claims_policy = "default" # 可选;引用 Authelia 项目的 claims policy机密客户端只接受密钥摘要,不接受明文;客户端应用保存生成摘要时对应的明文。
公共客户端不配置摘要,必须启用 S256 PKCE 并使用 none token endpoint 认证。
回调 URI 是区分大小写的完整 URI,非回环 HTTP 回调会被拒绝。OIDC 客户端的
authorization_policy 与用于 ForwardAuth 的 access_control.default_policy
相互独立。
应用客户端映射作为 app IR 的受管元数据存入该应用 .env,以便独立导出。
部署 app 时,编排层将它原子同步为
authelia/config/oidc-clients/<应用项目>.yml;取消声明或删除 app 时删除同名
片段。重新部署 Authelia 时会从所有现有 app IR 重建该目录。跨项目校验保证
client_id 全局唯一;已部署的 Authelia 未启用 [oidc] 时拒绝新客户端。
片段变更后会明确提示重启 Authelia,不隐式改变其运行状态。
users 下的 TOML 表名是认证用户名:[users.admin] 生成用户名 admin;
display_name 只用于展示,email 默认也不作为登录别名。修改登录名必须修改表名,
例如将其改为 [users.nihilityer],再执行 up --force 与 restart authelia。
用户名改名按删除旧用户并新增用户处理,不隐式迁移以旧用户名关联的认证状态。
生成固定名 authelia 项目,使用文件用户库、SQLite 和文件型密钥。配置与用户库
位于项目目录并只读挂载,密钥文件使用 0600 且只读挂载;SQLite、TOTP 等运行
状态位于第一个 data_root 的 authelia/,删除项目不会删除认证状态。用户库由
TOML 声明管理,watch = false,并禁用容器内密码修改与重置,避免运行时文件和
导出状态形成双重真相。用户名或密码哈希变更后必须显式重启容器加载新用户库。
storage_encryption_key 是持久化状态的加密根密钥,数据库初始化后必须保持稳定;
轮换必须先用旧密钥执行 Authelia 的 storage encryption change-key,不能仅重写配置。
可选的 [telemetry] 只生成 telemetry.metrics.address 与 telemetry.tracing.*:
Authelia 4.39.x 的指标路径固定为 /metrics,写出的 telemetry.metrics.path 会被
当成未知配置键并 fatal 退出(configuration key not expected: telemetry.metrics.path),
容器随即进入无限重启。声明里的 metrics_path 仍被接受并原样保留在导出结果中
(export → up 无损),但不是 /metrics 时只记一条警告,不写入 YAML。
二次验证固定使用 TOTP:将其显式启用并设为默认方法,同时禁用 WebAuthn;是否要求
二次验证仍分别由 ForwardAuth 的 default_policy 和 OIDC 客户端的
authorization_policy 决定。
启用 OIDC 时,模板额外生成 OIDC_HMAC_SECRET 与 OIDC_JWK_PRIVATE_KEY 两个
0600 文件,通过 Authelia template 配置过滤器从只读 /secrets 挂载读取;
configuration.yml 不包含这两个 provider 密钥的明文。它在模板过滤阶段遍历各应用
片段,为 Authelia 构造单一 identity_providers.oidc.clients 列表;provider 配置仍
持久化在 Authelia 项目的受管 .env 中以支持独立导出。
claims_policies 是 provider 级结构,应用片段无法贡献,且 Authelia 的配置模板
过滤器不支持命名模板,因此由模板直接内联到生成的 YAML 中;没有已声明的客户端时
整个 identity_providers 块都不会生成,也就不需要 provider 密钥。应用片段目录
为空时不会产生空的 clients 键。
Traefik 模板始终生成 authelia@file ForwardAuth 中间件,地址固定为共享
nsetup-proxy 网络内的 http://authelia:9091/api/authz/forward-auth。普通应用在
路由中使用 middlewares = ["authelia"] 即可启用认证;认证门户自身不使用该
中间件,避免重定向循环。
format = 1
template = "static"
name = "docs"
host = "docs"
version = "1.27"
middlewares = ["gzip"]站点文件不写入 TOML,由 up --assets <目录> 随请求上传,落盘到项目目录的
site/,再挂到 nginx 的 /usr/share/nginx/html。容器同时以只读方式获得整个
项目目录(/opt/nsetup),把 nginx.conf 放进项目目录即可改写服务方式;默认
站点配置由模板拥有的 config/nginx/default.conf 提供。--assets-mode merge
(默认)只覆盖同名文件,--assets-mode replace 才清空站点目录;上传文件使用
0644、目录 0755,使容器内非 root 进程可以读取。
CLI 标志类命令(如 edit 之外的创建入口)在客户端构建 TOML;daemon 侧只有
两条输入路径:TOML(Apply)与 compose 文件(ImportCompose),二者都汇入
IR 后由唯一的写盘收口生成项目。
flowchart LR
A["nsetup up -f app.toml<br/>(含 authelia / traefik / static 模板)"] --> AP["Apply RPC<br/>config_toml (+assets)"]
B["nsetup import name -f compose.yaml"] --> IC["ImportCompose RPC"]
AP -->|解析 TOML → 模板生成| IR["StackSpec (IR)"]
IC -->|解析 compose<br/>未知字段报错| IR
IR --> D["deploy 收口<br/>白名单校验 → 规范化生成 → 原子写<br/>→ compose config 验证 → 失败回滚"]
D --> E[("stacks_root/名称/<br/>compose.yaml + .env")]
sequenceDiagram
participant C as CLI
participant D as daemon
participant IR as StackSpec
participant F as compose.yaml + .env
participant K as docker compose
C->>D: nsetup edit(含 --version 升级)
D->>F: 读取
F->>IR: 解析 compose + .env,反解语义视图
D->>IR: 应用修改(字段 / set_routes / set_image_version)
IR->>D: 重新生成规范 YAML
D->>F: 原子写(验证失败自动回滚)
D->>K: compose up -d [service]
nsetup export:解析项目的 compose + .env,反解出完整 TOML(app 模板还原
各服务字段与路由选项;traefik / static 模板还原模板参数)。导出内容永远等于
当前真实状态,可直接用于 up 重建项目。
远程访问是全局标志而非独立命令树:任何命令都可加 --endpoint <地址> --token-file <路径> 连接远程 daemon,本机缺省走 Unix socket。
| 命令 | 说明 |
|---|---|
nsetup init [--domain D] [--stacks-root P] [--data-root P]... [--docker-socket P] [--force] |
安装 daemon:写配置、安装二进制与 systemd unit、启动服务。--force 覆盖已存在的安装,未指定的配置项沿用已有值 |
nsetup status |
daemon 版本、Docker 连通性、项目根目录、主域名 |
nsetup template [app|authelia|traefik|static] |
输出带注释的配置骨架 |
nsetup up -f <配置.toml> [--assets <目录>] [--start] [--force] |
创建或整体更新项目;流式返回排队、执行与完成阶段 |
nsetup import <名称> -f <compose.yaml> [--env-file <路径>] [--start] |
导入 compose 文件(字段子集),新建或整体替换 |
nsetup export <名称> [-o <输出.toml>] |
导出当前状态的 TOML |
nsetup edit <名称> [--service <服务>] [选项...] |
局部修改:--version(升级镜像)、--image、--port、--publish、--volume、--env、--host、--middleware、--label、--healthcheck-cmd、--remove-healthcheck、--start 等;未指定的项保持不变 |
nsetup list / nsetup show <名称> |
项目列表 / 项目与容器详情 |
nsetup start / stop / restart <名称> |
流式显示生命周期阶段;停止与重启使用 30 秒容器关闭上限 |
nsetup pull <名称> |
拉取镜像;TTY 原地刷新单行进度,管道保留逐事件稳定输出 |
nsetup build <名称> |
构建镜像 |
nsetup logs <名称> [--tail N] [-f] |
只读日志流;不占用变更锁,客户端断开后终止 Compose 子进程 |
nsetup rm <名称> [--force] |
停止并删除项目(--force 跳过确认);绑定挂载的数据不受影响 |
nsetup daemon 为隐藏命令,仅供 systemd unit 的 ExecStart 调用。daemon 的
启停与开机自启用 systemctl 管理。
proto 定义与命令一一对应,共 14 个方法。声明式输入一律是 TOML 字符串;只有
Edit 使用类型化字段表达局部修改:
service Orchestrator {
rpc Status(StatusRequest) returns (StatusResponse);
rpc Apply(ApplyRequest) returns (stream OperationProgress);
rpc ImportCompose(ImportComposeRequest) returns (stream OperationProgress);
rpc Export(ExportRequest) returns (ExportResponse);
rpc Edit(EditRequest) returns (stream OperationProgress);
rpc List(ListRequest) returns (ListResponse);
rpc Get(GetRequest) returns (Stack);
rpc Remove(RemoveRequest) returns (stream OperationProgress);
rpc Start(ActionRequest) returns (stream OperationProgress);
rpc Stop(ActionRequest) returns (stream OperationProgress);
rpc Restart(ActionRequest) returns (stream OperationProgress);
rpc Pull(ActionRequest) returns (stream PullProgress);
rpc Build(ActionRequest) returns (stream OperationProgress);
rpc Logs(LogsRequest) returns (stream LogLine);
}
message ApplyRequest {
string config_toml = 1;
repeated Asset assets = 2; // static 模板的站点文件
bool start = 3;
bool force = 4; // 覆盖已存在的项目
}
message ImportComposeRequest {
string name = 1;
string compose_yaml = 2;
optional string env_file = 3; // 缺省:新项目为空,已有项目沿用原文件
bool start = 4;
}
message EditRequest {
string name = 1;
optional string service = 2; // 单服务项目可省略
optional string image = 3;
optional string version = 4;
repeated string command = 5;
optional uint32 container_port = 6;
repeated Route routes = 7; // 提供时整体重建路由
repeated PublishedPort published_ports = 8;
repeated Volume volumes = 9; // 追加
map<string, string> environment = 10; // 合并
optional NetworkMode network_mode = 11;
optional string external_network = 12;
repeated Middleware middlewares = 13;
repeated string labels = 14; // 按键替换
optional Healthcheck healthcheck = 15;
bool remove_healthcheck = 16;
bool start = 17;
}/etc/nsetup/config.toml,扁平键:
domain = "example.com" # 短子域名拼接的主域名
stacks_root = "/var/lib/nsetup/stacks" # 项目根目录
data_roots = ["/var/lib/nsetup/data"] # bind mount 允许的路径前缀
listen = "unix:///run/nsetup/nsetup.sock" # gRPC 监听;TCP 地址需配合 auth token
docker_socket = "/var/run/docker.sock" # Docker API socketbind mount 白名单 = data_roots ∪ stacks_root ∪ docker_socket(精确匹配),
完全由配置推导。
| 内容 | 路径 |
|---|---|
| 二进制 | /usr/local/bin/nsetup |
| systemd unit | /etc/systemd/system/nsetup.service |
| 配置文件 | /etc/nsetup/config.toml(root:nihility、0640) |
| TCP 认证 token | /etc/nsetup/auth.token(0600) |
| gRPC socket | /run/nsetup/nsetup.sock(root:nihility、0660) |
| 项目 | stacks_root/<名称>/{compose.yaml, .env, config/, secrets/, site/, files/} |
项目 --files 上传内容 |
stacks_root/<名称>/files/(0644/0755,容器内挂到 /opt/nsetup/files) |
| Traefik 动态配置 | stacks_root/traefik/config/dynamic/{nsetup.yml, custom.yml}(providers.file.directory) |
| Authelia 状态 | data_roots[0]/authelia/{db.sqlite3, notification.txt} |
| 模块 | 职责 |
|---|---|
cli |
CLI 稳定入口,重新导出参数模型和命令执行器 |
cli/args |
clap 参数、子命令以及中文帮助模板 |
cli/runner |
本地命令和远程 RPC 命令分发 |
cli/edit |
编辑参数到类型化 protobuf 请求的转换 |
cli/io |
配置、静态资源、导出文件与 stdout 操作 |
config |
配置文件模型、加载、白名单推导 |
install |
init:自安装与 systemd unit 管理 |
spec |
IR 公共数据模型与稳定入口;具体职责拆入 spec/project、spec/service、spec/value |
spec/project |
项目级 compose YAML、.env 加载、校验与规范化序列化 |
spec/service |
服务镜像版本与 Traefik label 语义视图的双向转换 |
spec/value |
路由、端口、bind mount、健康检查、镜像与名称值对象校验 |
template |
模板公共 TOML 模型、注册表与稳定入口 |
template/authelia |
Authelia TOML ↔ IR、服务定义与基础配置生成 |
template/authelia/oidc |
Authelia OIDC provider 校验、配置与文件型密钥 |
template/authelia/users |
文件用户库模型、校验与 YAML 生成 |
template/oidc |
app 拥有的 Authelia OIDC 客户端模型、校验与 YAML 片段 |
template/generate |
app/traefik/static TOML → IR,并生成模板附属文件 |
template/reverse |
当前 IR → 规范化 TOML,恢复模板参数与服务语义 |
template/skeleton |
CLI 输出的带注释 TOML 配置骨架 |
orchestrator |
编排器及其请求、响应公共模型 |
orchestrator/operations |
应用、导入、查询和项目生命周期操作 |
orchestrator/edit |
服务局部编辑、标签合并和网络语义修改 |
orchestrator/oidc |
跨项目 OIDC client ID 校验及 app 片段向 Authelia 同步 |
orchestrator/deploy |
deploy 写盘收口、冲突检查和受管目录解析 |
orchestrator/storage |
安全路径、附属文件复制、原子文件写入和回滚辅助 |
docker |
docker compose 子进程封装(统一使用配置的 docker_socket) |
rpc |
生成协议模块及 RPC 稳定入口 |
rpc/service |
gRPC 服务方法、变更互斥、只读并发和流式阶段调度 |
rpc/client |
Unix socket 与认证 TCP 客户端 |
rpc/transport |
UDS/TCP 监听、Bearer 认证和关闭信号 |
rpc/conversion |
protobuf 线路表示与领域模型之间的转换和校验 |
升级到 0.2.0 时注意以下行为变化:
| 变化 | 说明 |
|---|---|
| Traefik 动态配置改为目录 | config/dynamic.yml 变为 config/dynamic/nsetup.yml,启动参数改用 --providers.file.directory;同目录的 custom.yml 与其它 *.yml 不会被 up 覆盖。重新部署 traefik 项目即可迁移。 |
| 指标默认开启 | traefik 模板默认 metrics = true,在内网入口暴露 Prometheus 指标并绑定到宿主机回环地址;不需要时显式写 metrics = false。 |
| 静态站点挂载点变化 | 项目目录整体挂到 /opt/nsetup,站点根目录由 /usr/share/nginx/html 变为 /opt/nsetup/site。 |
| 路由冲突判定放宽 | 冲突按 host + path_prefix + entrypoint + protocol 判定,原先因同 host 被判重的配置升级后可以直接应用。 |
| OIDC 客户端 PKCE 字段 | 仅在 require_pkce = true 时输出 pkce_challenge_method: S256,不再对全部客户端强制 PKCE。 |
| 应用后重建容器 | 部署会整体替换项目目录,up --start(以及 edit --start)在项目有运行中容器时使用 --force-recreate,避免容器继续持有已被替换的目录 inode。 |
| RPC 线路协议变化 | Route.middlewares 与 EditRequest.middlewares 由枚举改为字符串,并新增 Doctor、SetDomain 两个 RPC。升级时 CLI 与 daemon 必须来自同一版本;跨版本混用时新客户端调用新 RPC 会得到 unimplemented,中间件参数会自行校验失败而不是静默丢弃。 |
升级到 0.2.1 时注意以下行为变化:
| 变化 | 说明 |
|---|---|
traefik / authelia 声明可省略 name |
两个模板的项目名固定为 traefik / authelia,name 可省略也可显式写出;写其它值会明确报错。0.2.0 中这两个模板任何情况都无法应用,0.2.1 修复。nsetup template traefik|authelia 的骨架与 nsetup export 的产出都能直接 nsetup up。 |
--files / --assets 权限改为 a+rX |
上传资源目录由 0750 改为 0755、文件 0644,容器内非 root 进程(如 nginx worker)可以直接读取;需要收紧时用 nsetup up --assets-perms private(0750/0640)。 |
static 模板支持 user / group_add / [hooks] |
站点侧可以像 app 模板一样在 TOML 内修正属主与权限,不必再维护 chmod 兜底脚本。 |
nsetup show --routes 完整 |
修复了 router 前缀拼接错误:模板生成的路由(来源 nsetup)与用户手写 label 路由(来源 labels)会一起列出,并补上 BACKEND 与 PRIORITY 列。 |
volumes 支持相对项目目录的挂载源 |
files/x.yaml、./config 与绝对路径等价,stacks_root 变更时不需要改仓库里的 TOML;命名卷与 ../ 仍被拒绝,展开后仍走白名单。 |
新增 up --files-only |
只重新上传 --files / --assets 并保留现有容器(项目目录 inode 不变),用于同步配置文件内容;不触碰 compose.yaml、.env,也不执行钩子与 OIDC 同步。 |
| 钩子输出与失败提示 | 钩子的 stdout/stderr 会作为进度信息回显;失败时的错误包含退出码、完整输出、容器当前状态与补救命令。nsetup up --help 说明钩子运行环境与 /tmp 只读。 |
Authelia /config 改为可写挂载 |
官方镜像 entrypoint 会执行 chown -R ${PUID}:${PGID} /config(镜像默认 0:0),只读挂载会让每次启动都往容器日志写 chown: ... Read-only file system;/secrets 保持只读。OIDC 片段改为原地重写,不再替换 inode。 |
--restart-dependents 文案 |
实际执行了重启时提示「已重启 authelia 使新客户端生效」,未重启时附带确切命令 nsetup restart authelia。 |
升级到 0.2.2 时注意以下行为变化(全部来自 0.2.1 的实测反馈):
| 变化 | 说明 |
|---|---|
Authelia 不再生成 telemetry.metrics.path |
metrics_path 仍可写出、仍原样保留在 export 结果里,但不再进入 configuration.yml;Authelia 4.39.x 的指标路径固定为 /metrics,多出该键会让容器以 configuration key not expected: telemetry.metrics.path fatal 退出并无限重启。写上非 /metrics 的值时只记一条 warning。 |
| traefik dashboard 只保留一条路由 | 不再额外生成把容器 8080 当后端的应用式路由。dashboard 固定 api@internal、priority 1000、internal-only@file(开启 dashboard_authelia 时再叠加 authelia@file),并显式声明主域名通配符证书域名。升级后重新 nsetup up -f traefik.toml --force --start,原先为绕开 404 而在 dynamic/custom.yml 里补的 dashboard-managed router 应当删除。 |
| 指标入口自动生成 router | metrics = true 时 config/dynamic/nsetup.yml 里生成 Path(/metrics) → prometheus@internal 与 PathPrefix(/api) || PathPrefix(/debug) → api@internal;nsetup doctor 因此不再降级为仅检查容器 label。为绕开 404 而在 custom.yml 里补的 metrics-internal / api-internal router 应当删除。 |
内置 tls 中间件保证存在 |
tls 是 HSTS 响应头中间件,继续随 nsetup.yml 生成,历史配置里的 middlewares = ["tls"] 不需要改;在 custom.yml 里补的 tls 定义应当删除(同名 file provider 中间件会被后加载的文件覆盖)。骨架与文档补上「不是路由级 TLS 开关」的说明。 |
traefik 健康检查带 --ping |
健康检查固定为 CMD ["traefik", "healthcheck", "--ping"],容器不再恒为 unhealthy;用 nsetup edit … --healthcheck-cmd 打的补丁不再需要。 |
nsetup doctor 归一化 router 名 |
比对前去掉 Traefik API 名字里的 @<provider> 后缀,不再把正常路由同时报成「未被加载」和「已不再声明」,完整报告重新可用。 |
| traefik 路由表 BACKEND 显示内置服务 | nsetup show traefik --routes 里 dashboard 的后端显示为 api@internal(不带端口),容器路由仍是 服务:端口。 |
升级到 0.2.3 时注意以下行为变化(来自 0.2.2 的实测反馈):
| 变化 | 说明 |
|---|---|
播种的 dynamic/custom.yml 只含注释 |
骨架不再写 http: {}、routers: {}、services: {}、middlewares: {}:Traefik 的 structures 解码器把空映射判定为 standalone element,file provider 按整目录构建,一个文件解析失败就让同目录 nsetup.yml 里的 metrics / api 路由与内置 tls 中间件一起失效(/metrics 404、响应缺 HSTS、doctor 降级),而容器健康检查只看 --ping,仍然是 healthy。注释与空文件都能正常加载,示例因此全部保持注释状态。 |
| 旧骨架自动升级 | 0.2.0–0.2.2 播种的 custom.yml 会在下次 nsetup up(含 --force)时换成只含注释的新骨架,否则升级后故障仍在。只有内容与旧模板逐字节相同才替换;用户改过的内容仍逐字节保留。改过、但里面还留着空映射的文件不会被改写,需要手工删掉那些行。 |
config/dynamic 只保留两个文件 |
该目录是受管目录,nsetup up 会整体重写它,nsetup.yml 由模板拥有、custom.yml 由用户拥有,其余 *.yml 一律清除。0.2.0 起的骨架注释曾称「同目录新增其它 *.yml 效果相同」,该说法不成立,手工配置必须写在 custom.yml 里。 |
- C1 镜像钉版本:所有镜像必须带明确标签,拒绝
latest与缺省标签;标签 须以字母、数字或下划线开头,仅含字母、数字、点、下划线、连字符,不超过 128 字符。 - C2 校验先行:项目名(小写字母开头,仅小写字母/数字/
-/_,≤63 字符)、 服务名、镜像引用、域名、宿主端口与路由冲突都在写入前校验,失败无副作用。 - C3 写盘单点收口:所有 compose 写入经过同一个 deploy 收口:白名单校验 →
规范化生成 → 原子写 →
docker compose config验证 → 失败恢复原文件。 - C4 密钥与权限:token、ACME、Authelia 密钥等写入
.env(0600),不进入 命令行参数;Authelia 额外生成0600密钥文件并只读挂载,容器环境只保存_FILE路径,生成的configuration.yml同样不含密钥明文;socket 与配置文件 权限见磁盘布局表。 - C5 compose 字段子集:只接受 IR 模型覆盖的 compose 指令,未知字段导入时 报错。这保证任何项目都能被解析回 IR 进行编辑与导出。
- C6 数据路径白名单:bind mount 源路径可以写成绝对路径(
data_roots、stacks_root或docker_socket内),也可以写成相对受管项目目录的路径 (files/x.yaml、./config)。相对写法在生成 Compose 之前展开为项目目录下的 绝对路径,再与绝对路径一起做归一化(解析./..、符号链接)与白名单校验。 命名卷(mydata:/data)与越出项目目录的../仍然报错。白名单完全由配置 推导,对所有项目(含 Traefik)一视同仁。多个应用挂载同一路径以共享数据是 合法用法。Docker 启动容器时会自动创建缺失的宿主目录,因此校验发生在任何compose up之前。 - C7 禁止命名卷:持久化数据一律使用白名单内的 bind mount,位置可审计、
可备份。compose 顶层
volumes段与NAME:/path语法在导入时报错。 删除项目只停止容器并删除项目目录,绑定挂载的数据不受影响。 - C8 变量镜像只读:镜像引用含环境变量(如 Traefik 的
${TRAEFIK_VERSION}或 Authelia 的${AUTHELIA_VERSION})的项目不能用edit --version升级;其版本由所属模板的参数管理,通过up --force整体应用。 - C9 操作可观测与可取消:变更 RPC 在等待锁、执行和完成时发送阶段事件;中间 事件写 stderr,最终结果保持 stdout 稳定。查询和日志不占用变更锁;持续日志与 拉取每 200ms 检查客户端连接,断开时终止所属 Compose 子进程。