nemu-x 说明 mikan 用户导入功能的审查修正要点

nemu-x 在 mikan(mihomo 内核 VPN 面板)Marzban/PasarGuard/Remnawave 用户导入 PR #37 中说明已 rebase 到 dev 并逐条修正评审意见:schema 移入 postgres/0003_legacy_sub_tokens.sql 且带 Down(若 #31 先合并则改号 0004),SQLite 导入测试回滚到导入基线;Reissue 在同一事务删除旧 legacy_sub_tokens;shortUuid 须匹配 ^[A-Za-z0-9_-]{16,128}$,否则用户导入后无链接;分页按 len(page.Users) 推进、空页或到 total 停止,少于 total 报 import_incomplete;预览亦为后台任务(POST /import/preview 返 202,结果走 GET /import/status),名称按 1000 一批检查,各列表上限 50 条加计数;RefillFor(n) 导入前补一次槽位并发一次 SlotsChanged;混用类型返回 409 import_kind_conflict,Remnawave 导入不改 legacy_sub_kind;上游两 API 均无 sub_revoked_at,故每条链接记录 not_before 并拒绝更早签发的 token,此后的吊销无法感知,已在包文档与预览警告提示重新签发;任务 panic 记为 import_failed、DELETE /import 可取消、Run 作为面板 worker 在关闭时取消并等待,中途取消或超时保留已建用户与报告;并修正 on-hold 溢出与地址校验细节。28 个 Go 包对 PostgreSQL 18 全通过,Web 应用类型检查与构建通过。

作者原文

Thanks. Rebased on dev (ea9db8a, after #30); every point is addressed in f8e5ed3 and 0bd2fd3.

Blocker: schema

  • 0001_baseline.sql and postgres_test.go are back to dev, and migrations/0019_legacy_sub_tokens.sql is gone.
  • The table is in postgres/0003_legacy_sub_tokens.sql, with a Down. #31 also adds a 0003_promocodes.sql, so whichever merges second has to renumber. I will move mine to 0004 if #31 goes first.
  • The store's SQLite import tests ran into a fully migrated schema and failed on the new table. They now roll the fixture back to the import baseline, which is what Migrate and RestoreSQLite do.

High

  1. Reissue deletes the user's legacy_sub_tokens in the same transaction (TestReissueDropsOldLinks).
  2. Weak shortUuid. A token must match ^[A-Za-z0-9_-]{16,128}$. Otherwise the user is imported without a link and listed under "no link" in the preview and the report (TestRemnawaveWeakToken).
  3. Paging. The offset advances by len(page.Users), and the loop stops on an empty page or at total. Fewer users than total fails the job with import_incomplete (TestFetchMarzbanCappedLimit, where the fake caps every page at 2).

Medium

  • Preview is a background job too. POST /import/preview returns 202 and the result arrives in GET /import/status. Names are checked with name = ANY($1) in chunks of 1000. The taken, invalid, skipped, no-link and failed lists are capped at 50 names plus a count.
  • Slots. Users.RefillFor(n) refills the missing slots once before the import and sends one SlotsChanged (TestRefillForTheImport).
  • Mixing kinds. A Marzban import over PasarGuard links, or the other way round, is refused with 409 import_kind_conflict (TestImportRefusesMixingSignedPanels). Remnawave links need no secret, so a Remnawave import leaves legacy_sub_kind alone.
  • sub_revoked_at is in neither Marzban's nor PasarGuard's API. What I did:
  • Each link stores not_before, the user's creation time in the old panel, and a token signed before it is refused.
  • A revoke after that cannot be seen. The package doc and the preview warning say so, and tell the admin to reissue those users' links.
  • Job.
  • recover turns a panic into a failed job (import_failed).
  • DELETE /import cancels a running job.
  • Importer.Run is one of the panel's workers: on shutdown it cancels the job and waits for it.
  • A cancel or timeout mid-import keeps the users made and their report, and Done still runs whenever users were created.
  • Tests: TestJobCancel, TestJobCancelKeepsThePartialReport, TestJobPanic, TestJobTimeout and TestJobStopsWithThePanel.

Low

  • On-hold overflow. The duration is checked against the 20-year limit before the multiplication, and out of range is invalid (TestOnHoldOverflow).
  • AddressOK blocks fd00:ec2::254. NAT64 64:ff9b::/96 is checked by the IPv4 inside it, so a NAT64 form of 169.254.169.254 is refused and one of 10.0.0.1 is allowed. CGNAT stays allowed, with a comment: Tailscale and similar overlays put an old panel there (TestAddressOKSpecial).
  • API errors are codes only. The remote text and Location go to the log.

All 28 Go packages pass in a Linux container against PostgreSQL 18. The web app typechecks and builds.