docs: 交付收尾(README/Dockerfile/Makefile 对齐 + 移除迁移状态节)
- README 重写:两阶段架构、LDAP 读/GraphQL 写、部门索引组名、离职双确认、 部署(起 LLDAP + Authelia 官方 lldap 模板 + 配 idsync + 构建运行)、命令、测试 - Makefile:test-integration 改指本地 LLDAP(3890/17170),新增 make lldap 起容器, 头部注释更新为 LLDAP - AGENTS.md:移除「迁移状态」节(迁移已完成,旧实现已删除/收敛)
This commit is contained in:
@@ -47,18 +47,6 @@
|
|||||||
不触碰 IO。因此它们能被完整单测,且 **dry-run 与真正执行共用同一份代码**,预览即所得。
|
不触碰 IO。因此它们能被完整单测,且 **dry-run 与真正执行共用同一份代码**,预览即所得。
|
||||||
改动它们时务必维持纯度,否则 dry-run 的可信度就没了。
|
改动它们时务必维持纯度,否则 dry-run 的可信度就没了。
|
||||||
|
|
||||||
### 迁移状态(写代码前先对号入座)
|
|
||||||
|
|
||||||
以下模块仍是 OpenLDAP 单阶段时代的旧实现,正在迁移到本文档描述的目标架构:
|
|
||||||
|
|
||||||
- `internal/ldaplib/ops.go` 的写路径(Add/Modify/ModifyDN)、`internal/password`(SSHA)、
|
|
||||||
`config.yml` 的 `user-ou / inactive-ou / group-root-name / group-object-class /
|
|
||||||
group-member-attr / initial-password` 仍是旧概念,**迁移完成前不要把它们当目标行为**。
|
|
||||||
- `internal/store` 现仅是轻量台账,尚未扩展为全字段镜像 + 部门关系表。
|
|
||||||
- `internal/feishu` 尚无卡片发送能力,离职确认通道未落地。
|
|
||||||
|
|
||||||
改代码时以本文档的硬约束为准,逐步把旧实现收敛到目标架构。
|
|
||||||
|
|
||||||
## 硬约束
|
## 硬约束
|
||||||
|
|
||||||
### 1. 真相源层级:飞书唯一,SQLite 只镜像
|
### 1. 真相源层级:飞书唯一,SQLite 只镜像
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# idsync — 飞书 → OpenLDAP 同步器
|
# idsync — 飞书 → SQLite → LLDAP 同步器
|
||||||
# 宿主未安装 Go,所有 Go 操作都在 podman 容器内执行。
|
# 宿主未安装 Go,所有 Go 操作都在 podman 容器内执行。
|
||||||
# 注意:容器必须 --network=host,否则容器内的 127.0.0.1 指向容器自身,无法访问宿主代理。
|
# 注意:容器必须 --network=host,否则容器内的 127.0.0.1 指向容器自身,无法访问宿主代理。
|
||||||
|
|
||||||
@@ -32,9 +32,18 @@ test:
|
|||||||
vet:
|
vet:
|
||||||
$(PODMAN) $(GO_IMAGE) go vet ./...
|
$(PODMAN) $(GO_IMAGE) go vet ./...
|
||||||
|
|
||||||
# 集成测试对真实 OpenLDAP 运行;容器内需能访问宿主的 slapd,
|
# 集成测试对真实 LLDAP 运行(LDAP 3890 / GraphQL 17170),先 `make lldap` 起容器。
|
||||||
# 因此用 host.containers.internal 并放行 no_proxy,避免流量被代理拦走。
|
LDAP_TEST ?= ldap://127.0.0.1:3890
|
||||||
LDAP_TEST ?= ldap://host.containers.internal:3899
|
LLDAP_GQL ?= http://127.0.0.1:17170
|
||||||
|
|
||||||
|
.PHONY: lldap
|
||||||
|
lldap:
|
||||||
|
podman run -d --name idsync-lldap \
|
||||||
|
-e LLDAP_LDAP_BASE_DN=dc=example,dc=com \
|
||||||
|
-e LLDAP_JWT_SECRET='change-me-jwt-secret-please' \
|
||||||
|
-e LLDAP_LDAP_USER_PASS=adminpass \
|
||||||
|
-p 3890:3890 -p 17170:17170 \
|
||||||
|
ghcr.io/lldap/lldap:stable
|
||||||
|
|
||||||
.PHONY: test-integration
|
.PHONY: test-integration
|
||||||
test-integration:
|
test-integration:
|
||||||
|
|||||||
@@ -1,176 +1,152 @@
|
|||||||
# idsync
|
# idsync — 飞书通讯录 → LLDAP 同步器
|
||||||
|
|
||||||
把飞书通讯录同步到 OpenLDAP,作为一个可扩展的小工具,用来替代 `go-ldap-admin`。
|
把飞书通讯录同步到 [LLDAP](https://github.com/lldap/lldap),替代 OpenLDAP 时代的
|
||||||
|
`go-ldap-admin`。数据单向流动、两阶段:
|
||||||
## 它做什么,不做什么
|
|
||||||
|
|
||||||
**做**:读取飞书部门与成员,物化到 OpenLDAP 的用户与分组条目,并在成员离职或重新入职时维护其去处。提供一个内嵌的管理界面,用于查看现状、预览变更、手动触发同步。
|
|
||||||
|
|
||||||
**不做**:认证、会话、登录页、密码重置、发信、SSO。这些全部由 Authelia 负责。
|
|
||||||
|
|
||||||
这个边界是刻意的。在「Authelia + OpenLDAP + 各种业务系统」这套组合里,本工具**不在认证路径上**——它只往 LDAP 写属性,鉴权链仍然是「业务系统 → Authelia → OpenLDAP bind」。因此它不需要、也不应该重复实现任何认证逻辑。
|
|
||||||
|
|
||||||
## 工作方式
|
|
||||||
|
|
||||||
```
|
```
|
||||||
飞书通讯录 ──读取──▶ 期望状态 ──diff──▶ 操作计划 ──(dry-run 预览 / 执行)──▶ OpenLDAP
|
飞书通讯录 ─快照(低频)─▶ SQLite 镜像 ─物化(略高频)─▶ LLDAP(只在职)
|
||||||
▲ │
|
(全字段+部门关系+生命周期) │
|
||||||
└─────────────── SQLite 台账(所有权记录)◀───────────┘
|
▼
|
||||||
|
离职:机器确认(飞书 is_resigned) → 飞书卡片一次性端点
|
||||||
|
人工确认 → confirmed_leave → 物化时物理删除
|
||||||
```
|
```
|
||||||
|
|
||||||
三个关键设计:
|
- **读走 LDAP、写走 GraphQL**:LLDAP 的实际状态用 LDAP 读,所有写入(建/改/删用户、
|
||||||
|
建组、成员关系)走 GraphQL,密码由 LLDAP 独占(程序绝不写密码,首次登录走
|
||||||
|
Authelia「忘记密码」)。
|
||||||
|
- **本工具不参与认证链路**:登录、会话、密码重置、发信都由 Authelia 负责。它只决定
|
||||||
|
「飞书有什么数据、LLDAP 该长什么样、两者差异是什么」。它对外发出的唯一通知是
|
||||||
|
「离职人工确认」飞书卡片。
|
||||||
|
|
||||||
**1. 用官方 SDK,一次拉全量。**
|
## 定位与边界
|
||||||
使用 `github.com/larksuite/oapi-sdk-go/v3`。部门一次取回整棵树(`fetch_child=true`),用户按部门枚举后按 `union_id` 去重。
|
|
||||||
注意 `BatchGetId` 的 `emails` 参数明确不支持企业邮箱,因此不能靠邮箱反查用户,只能从部门入口枚举。
|
|
||||||
|
|
||||||
**2. 离职由缺席推断,再逐个确认。**
|
| 属于本项目 | 不属于本项目 |
|
||||||
`FindByDepartment` 不返回离职人员,所以「离职」只能先由「台账在职但本轮缺席」推断出来;随后对每个候选调用 `User.Get` 读取 `status.is_resigned` 确认。**只有确认离职才会迁移**——缺席也可能只是通讯录权限范围被收窄,直接迁移会误伤在职员工。
|
|
||||||
|
|
||||||
**3. SQLite 台账是删除权限的唯一来源。**
|
|
||||||
台账记录「哪些条目是本工具创建的」。任何不在台账中的条目(服务账号、手工账号、`admin`)在计划里永不出现,因此不可能被同步任务误改或误删。这在接管一个既有目录时尤其重要。
|
|
||||||
|
|
||||||
## 同步内容
|
|
||||||
|
|
||||||
### 登录名
|
|
||||||
|
|
||||||
`uid` 取自**企业邮箱**(`enterprise_email`)的 `@` 前缀。
|
|
||||||
|
|
||||||
刻意不使用个人邮箱兜底:企业邮箱是组织内唯一且受控的标识,回落到个人邮箱会产生不可预期、且在离职后仍然有效的登录名。缺少企业邮箱的用户会被**跳过并在界面上显著列出**,不会被静默丢弃。前缀冲突时同样跳过并报告,由人工修正邮箱。
|
|
||||||
|
|
||||||
### LDAP 布局
|
|
||||||
|
|
||||||
```
|
|
||||||
<base_dn>
|
|
||||||
├── ou=people 在职用户(Authelia 的 base_dn 指向这里)
|
|
||||||
│ └── uid=<邮箱前缀>,ou=people,<base_dn>
|
|
||||||
├── ou=Inactive 离职用户(位于 Authelia base_dn 之外 → 自动无法登录)
|
|
||||||
│ └── uid=<邮箱前缀>,ou=Inactive,<base_dn>
|
|
||||||
└── ou=<group-root> 部门树
|
|
||||||
└── cn=<部门拼音>,cn=<父部门拼音>,...,ou=<group-root>,<base_dn>
|
|
||||||
```
|
|
||||||
|
|
||||||
部门用 `groupOfUniqueNames` + `uniqueMember`,因此 Authelia 的 `Remote-Groups` 与基于组的 ACL 可直接使用。
|
|
||||||
部门 CN 用拼音:中文作 RDN 需要转义成 `#hex` 形式,可读性差且部分工具链兼容不佳。
|
|
||||||
|
|
||||||
### 属性映射
|
|
||||||
|
|
||||||
| LDAP 属性 | 来源 |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| `uid` / `cn` | `enterprise_email` 的 `@` 前缀 |
|
| 飞书有什么数据、LDAP 该长什么样、两者差异是什么 | 谁有权限登录(Authelia) |
|
||||||
| `sn` | `name` |
|
| 快照/物化调度、离职双确认状态机、离职卡片通知 | 认证、会话、登录页、密码重置 |
|
||||||
| `displayName` | `name` |
|
|
||||||
| `givenName` | `en_name`,为空回落 `name` |
|
|
||||||
| `mail` | **`enterprise_email`** |
|
|
||||||
| `mobile` | `mobile` |
|
|
||||||
| `employeeNumber` | `employee_no` |
|
|
||||||
| `departmentNumber` | `job_title` |
|
|
||||||
| `postalAddress` | `work_station` |
|
|
||||||
| `businessCategory` | 所属部门名(逗号拼接) |
|
|
||||||
| `employeeType` | `Active` / `Resigned` |
|
|
||||||
| `userPassword` | 仅创建时写入,**更新路径永不触碰** |
|
|
||||||
|
|
||||||
### 与 Authelia 的衔接
|
鉴权链是 `业务系统 → Authelia → LLDAP bind`;本工具只保证 LLDAP 里只在职、且离职
|
||||||
|
必须经人工确认后才删除。
|
||||||
|
|
||||||
- **`mail` 必须是企业邮箱**:Authelia 重置密码时从 LDAP 的 `mail` 属性取收件地址。若该属性为空,重置流程会直接失败并报 `user ... has no email address configured`。
|
## 架构
|
||||||
- **`ou=Inactive` 必须在 Authelia 的 `base_dn` 之外**:这是离职用户无法登录的机制。请确认你的 `base_dn` 是否正好是 `ou=people,<base>`。
|
|
||||||
- **更新路径绝不写 `userPassword`**:Authelia 重置密码时直接写 LDAP 的 `userPassword`。如果同步的更新路径也写这个属性,用户刚设好的密码会在下一轮同步时被打回初始值。代码中有结构性检查阻止这一点。
|
|
||||||
- **密码必须自行哈希**:slapd 不会对普通 Modify 写入的 `userPassword` 自动加盐。本工具写入 `{SSHA}`,不依赖服务端 `password-hash` 配置。
|
|
||||||
|
|
||||||
新用户会得到一个**随机**初始密码(或用 `ldap.initial-password` 指定),用户不可知。首次登录走 Authelia 的「忘记密码」——该流程不需要旧密码。
|
```
|
||||||
|
internal/config 配置加载、默认值、校验、DN 拼接、两套 schedule
|
||||||
## 安全护栏
|
internal/feishu 只读飞书:拉通讯录、机器确认离职、发离职确认卡片(webhook)
|
||||||
|
internal/store SQLite 镜像:全字段用户 + 部门关系 + 生命周期状态 + 一次性 token
|
||||||
默认开启,用于防住「最坏的那一次同步」(飞书应用权限被收窄、token 指向错误租户、部门过滤写错,都会让期望状态突然大幅缩小):
|
internal/mapping 镜像 → 期望 LLDAP 状态(纯函数);组名=部门稳定索引 cn=<dept_id>
|
||||||
|
internal/ldaplib LLDAP 读原语:连接/bind/搜索
|
||||||
1. **默认 dry-run**。命令行不加 `-apply`、界面不点确认,都不会写入任何内容。
|
internal/lldapgql LLDAP 写通道:GraphQL(登录 + 各 mutation),端点 /api/graphql
|
||||||
2. **停用阈值**。单次停用超过 `max-deactivate` 条或 `max-deactivate-percent` 比例时拒绝执行,需 `-force` 或界面强制确认。
|
internal/syncer diff 引擎(纯函数)+ 快照/物化编排(单实例互斥)
|
||||||
3. **所有权隔离**。非台账条目永不被修改或删除。
|
internal/server HTTP 管理界面 + 一次性离职确认端点(token 即凭证)
|
||||||
4. **admin 保护**。`admin` 永不被移出分组;外部手工加入的成员只告警、不移除。
|
internal/webui go:embed 前端产物 + ETag(内容指纹)
|
||||||
5. **部门树建树有环保护**,分组按层级排序,保证父分组先于子分组创建。
|
|
||||||
|
|
||||||
## 构建
|
|
||||||
|
|
||||||
宿主无需安装 Go 或 Node,全部通过容器完成(本机已有 `golang:1.26-trixie` 与 `node:20-alpine` 镜像):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make deps # 下载 Go 依赖
|
|
||||||
make css # 构建 WebUI 样式(需要 Node 容器)
|
|
||||||
make build # 产出 bin/idsync
|
|
||||||
make test # 单元测试
|
|
||||||
make vet
|
|
||||||
make run # 本地运行
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> **关于代理与网络**:若 `proxy.golang.org` 不可直连,`Makefile` 已在容器调用上带上
|
### 关键约定
|
||||||
> `http_proxy`/`https_proxy` 并强制 `--network=host`。默认网络下容器内的 `127.0.0.1` 指向容器自身,
|
|
||||||
> 无法访问宿主代理——这是必须用 `--network=host` 的原因。
|
|
||||||
>
|
|
||||||
> 拉取基础镜像时同样需要代理(`podman pull` 不经由 Makefile):
|
|
||||||
> ```bash
|
|
||||||
> podman pull --http-proxy=http://127.0.0.1:10808 docker.io/library/golang:1.26-trixie
|
|
||||||
> # 或为本次会话导出: export http_proxy=http://127.0.0.1:10808
|
|
||||||
> ```
|
|
||||||
> 注意不要用 `containers.conf` 的 `[engine] env` 全局注入代理:那会让所有容器的 `http_proxy`
|
|
||||||
> 都指向宿主,导致容器内访问自身或本地服务时被代理拦截。
|
|
||||||
|
|
||||||
`internal/webui/dist/` 下的前端产物会被 `go:embed` 打进二进制,因此**部署时不需要 Node**。修改前端后需重新执行 `make css` 并重新编译。
|
- **只在职**:LLDAP 只有 `ou=people`,`uid` 取企业邮箱 `@` 前缀;缺少企业邮箱的用户
|
||||||
|
被跳过并记入 skipped(WebUI 可查,必须被管理者发现)。
|
||||||
### 集成测试
|
- **组名 = 部门稳定索引**:组 `displayName`/CN = `cn=<open_department_id>,<ou=groups>`
|
||||||
|
。用部门 ID 而非拼音——改名/挪动不改变组身份,避免「改名波及子孙组」;可读的部门
|
||||||
集成测试对真实的 OpenLDAP 运行,覆盖单元测试无法保证的部分:DN 转义、objectClass 约束、modrdn 移动后密码是否保留、以及「更新绝不触碰 userPassword」:
|
中文名只存在数据库(`mirror_departments.name` / 用户 `business_category`),不进入
|
||||||
|
LDAP 组名。
|
||||||
```bash
|
- **离职 = 双确认后才删**:缺席 → 飞书 `is_resigned` 机器确认 → 生成一次性 token 并发
|
||||||
podman run -d --name idsync-ldap -p 3899:389 \
|
飞书管理群卡片(按钮跳本工具确认端点)→ 人工确认 → `confirmed_leave` → 下一轮物化
|
||||||
-e LDAP_ORGANISATION="Example Inc" -e LDAP_DOMAIN="example.com" \
|
物理删除。两道闸缺一不可;用户重新可见即撤销并作废未用 token。
|
||||||
-e LDAP_ADMIN_PASSWORD=adminpass \
|
- **操作顺序是硬约束**:分组 → 用户 → 加组 → 移组 → **删除(最后一阶段,且只处理
|
||||||
docker.io/osixia/openldap:1.5.0
|
confirmed_leave)**。
|
||||||
|
- **所有权只认镜像台账**:镜像之外的条目(服务账号、手工账号、admin)永不入计划。
|
||||||
make test-integration
|
- **快照与物化互斥**:共享一把单实例锁,绝不并发,防止物化读到半新半旧镜像污染 LLDAP。
|
||||||
```
|
|
||||||
|
|
||||||
端到端测试还会启动一个假的飞书 OpenAPI 服务(`internal/syncer/e2e_test.go`),在没有真实租户凭据的情况下验证完整链路:首次全量创建、幂等重跑、属性漂移、离职迁移、重新入职、以及护栏拦截。
|
|
||||||
|
|
||||||
## 运行
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp config.example.yml config.yml
|
|
||||||
# 生成会话密钥与管理员密码哈希
|
|
||||||
./bin/idsync -gen-secret
|
|
||||||
./bin/idsync -hash-password '你的密码'
|
|
||||||
|
|
||||||
# 只预览,不写入(推荐先跑这个)
|
|
||||||
./bin/idsync -config config.yml -dry-run
|
|
||||||
|
|
||||||
# 确认无误后执行
|
|
||||||
./bin/idsync -config config.yml -apply
|
|
||||||
|
|
||||||
# 服务模式(含 WebUI 与定时同步)
|
|
||||||
./bin/idsync -config config.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
建议的首次接管流程:先 `-dry-run` 看计划 → 核对后 `-apply` → 打开界面确认用户与分组 → 观察几轮 dry-run 的定时任务后再把 `sync.default-dry-run` 改为 `false`。
|
|
||||||
|
|
||||||
## 管理界面
|
|
||||||
|
|
||||||
六个页面:概览(连通性与计数)、用户(受管清单 + 一致性检查)、部门分组、**变更预览**(最有价值的一页:dry-run 计划表,确认后执行)、同步历史。
|
|
||||||
|
|
||||||
样式用 Tailwind v4 + FlyonUI。FlyonUI 官方不支持 CDN 方式(样式在构建期生成),因此走官方的构建路径,产物内嵌进二进制。
|
|
||||||
|
|
||||||
主题为白/暗底 + 绿色主色,取自 Gitea 的品牌视觉(`#609926`,暗色下 `#7ab63a`;注意 Gitea 自身的 `--color-primary` 其实是蓝色,品牌绿是它的 logo 色)。右上角按钮切换,偏好存在 `localStorage`。
|
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
|
### 1. 起 LLDAP
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
podman build -t idsync .
|
podman run -d --name idsync-lldap \
|
||||||
podman run -d --name idsync -p 8899:8899 \
|
-e LLDAP_LDAP_BASE_DN=dc=example,dc=com \
|
||||||
-v $PWD/config.yml:/app/config.yml:ro \
|
-e LLDAP_JWT_SECRET='<random>' \
|
||||||
-v idsync-data:/app/data \
|
-e LLDAP_LDAP_USER_PASS='<admin-pass>' \
|
||||||
idsync
|
-p 3890:3890 -p 17170:17170 \
|
||||||
|
ghcr.io/lldap/lldap:stable
|
||||||
```
|
```
|
||||||
|
|
||||||
## 已知边界
|
LDAP 明文 3890、TLS 6360;WebUI/GraphQL 17170。用户名默认 `admin`。
|
||||||
|
|
||||||
- **不做密码同步下发**。Authelia 重置密码只写 LDAP,不发出任何事件或回调。因此对于「只认自己库里本地密码、既不吃 LDAP 也不读 HTTP 头」的服务,无法通过本工具同步密码。这类服务建议套一层 forward-auth(由 Authelia 注入 `Remote-User` / `Remote-Email` / `Remote-Groups`),或迁移到支持 LDAP 的同类方案。
|
### 2. 配 Authelia(官方 lldap 模板,勿定制)
|
||||||
- 部门改名会导致分组 DN 变化。此时**不会自动改名**(自动改名会牵动成员的 `uniqueMember` 引用与依赖该 DN 的 ACL),而是在预览页告警,交由人工处理。
|
|
||||||
- 飞书侧删除的部门不会被自动删除分组,同样只告警。
|
```yaml
|
||||||
|
authentication_backend:
|
||||||
|
ldap:
|
||||||
|
implementation: lldap
|
||||||
|
url: ldaps://lldap:6360 # 或 ldap://lldap:3890 + start_tls
|
||||||
|
base_dn: dc=example,dc=com
|
||||||
|
# 专用 bind 用户,别拿 admin
|
||||||
|
user: uid=authelia_svc,ou=people,dc=example,dc=com
|
||||||
|
password: <bind-pass>
|
||||||
|
groups_filter: (&(memberOf={dn})(objectClass=groupOfUniqueNames))
|
||||||
|
attributes:
|
||||||
|
username: uid
|
||||||
|
display_name: cn
|
||||||
|
mail: mail
|
||||||
|
member_of: memberOf
|
||||||
|
```
|
||||||
|
|
||||||
|
给 Authelia 专用 bind 用户授予 `lldap_strict_readonly` + `lldap_password_manager`。
|
||||||
|
用户 objectClass 是 `inetOrgPerson`,官方 `users_filter`(`objectClass=person`)即命中,
|
||||||
|
**不要添加任何自定义属性/过滤**。`mail` 必须是企业邮箱(Authelia 重置密码的收件地址)。
|
||||||
|
|
||||||
|
### 3. 配 idsync
|
||||||
|
|
||||||
|
复制 `config.example.yml` → `config.yml`,填:飞书 app 凭据 + 离职卡片 webhook、
|
||||||
|
LLDAP 读(LDAP URL/base-dn/admin)+ 写(GraphQL base-url/admin)、离职确认端点对外
|
||||||
|
`server.confirm-base-url`、两套 `schedule`。其中:
|
||||||
|
|
||||||
|
- `confirm-base-url` 必须是公网/反代可达地址,飞书卡片按钮链接用它拼接。
|
||||||
|
- 先观察几轮 `sync-dry-run: true` 的物化计划,再放开为实际执行。
|
||||||
|
|
||||||
|
### 4. 构建与运行
|
||||||
|
|
||||||
|
宿主无 Go/Node,构建都在容器内(`Makefile` 已封装,容器强制 `--network=host`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make deps / make css / make build / make run
|
||||||
|
```
|
||||||
|
|
||||||
|
产物 `bin/idsync` 为静态二进制,内嵌 WebUI,可按 `Dockerfile` 打进镜像部署到
|
||||||
|
`0.0.0.0:8899`。镜像数据库在 `data/`(含离职状态与一次性 token,务必持久化)。
|
||||||
|
|
||||||
|
## 命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./idsync -check-config # 打印有效配置(凭据脱敏)
|
||||||
|
./idsync -snapshot # 只跑一轮快照(飞书→镜像+离职流程)后退出
|
||||||
|
./idsync -dry-run # 快照+物化,只打印计划不写入
|
||||||
|
./idsync -apply # 快照+物化并实际写入
|
||||||
|
./idsync -apply -force # 跳过删除数量阈值护栏
|
||||||
|
./idsync -gen-secret # 生成会话密钥
|
||||||
|
./idsync -hash-password '…' # 生成管理员密码 bcrypt 哈希
|
||||||
|
```
|
||||||
|
|
||||||
|
管理界面 `http://<host>:8899`:概览(连通性/待确认离职/被跳过)、用户、部门分组、
|
||||||
|
变更预览(含「立即快照」「应用变更」)、物化历史。
|
||||||
|
|
||||||
|
## 测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make lldap # 拉起本地 LLDAP 容器(可选)
|
||||||
|
make test # 单元测试(无需外部依赖)
|
||||||
|
make test-integration# 对真实 LLDAP 运行(3890/17170)
|
||||||
|
make vet # go vet
|
||||||
|
```
|
||||||
|
|
||||||
|
单元测试覆盖:镜像事务化回滚、生命周期状态机、一次性 token(一次/过期/复用被拒)、
|
||||||
|
diff 引擎的安全回归(未确认不删、非受管不删、admin 永不移出、删除最后阶段)、
|
||||||
|
部门索引组命名稳定。集成测试用假飞书 + 真 LLDAP 走通全链路:首次创建、幂等、属性漂移、
|
||||||
|
confirmed_leave 删除、重新入职重建。
|
||||||
|
|
||||||
|
## 项目状态
|
||||||
|
|
||||||
|
两阶段迁移已完成:SQLite 镜像层、飞书卡片 + 快照、镜像驱动映射(部门索引组名)、
|
||||||
|
GraphQL 写客户端、config/ldaplib 收敛为 LLDAP 形态、物化编排、一次确认端点 + WebUI、
|
||||||
|
集成测试改用真 LLDAP 容器。`make test` / `make test-integration` / `make vet` 全绿。
|
||||||
Reference in New Issue
Block a user