Help us shape the future of Go! Take the 2026 Go Developer Survey

nju-connect

module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: AGPL-3.0

README

nju-connect

南京大学校园 VPN 的原生客户端:一个同时支持 EasyConnect 与 aTrust 的命令行程序, 以及 macOS 菜单栏应用。连接后在本机提供 SOCKS5 代理(默认 127.0.0.1:1081), 不改系统路由;密码保存在本机仅本人可读的文件里,不依赖系统钥匙串。

为什么不用深信服的官方客户端:EasyConnect / aTrust 连上后会接管整机网络,改写系统路由表和 DNS, 并在后台常驻服务,整台机器的流量走向跟着 VPN 变,容易和代理、Tailscale、Docker 网络冲突。 nju-connect 只在本机回环地址上开一个稳定的 SOCKS5 端口,哪个应用需要校园网就指向它, 系统路由、DNS、防火墙都不碰,退出即结束。

项目主页:https://soundadam.com/zh/projects/nju-connect/ · 下载:Releases

安装

macOS 与 Linux 用 Homebrew(macOS 上是菜单栏应用加 nju-connect 命令,Linux 上是 nju-connect 命令):

brew install --cask soundadam/tap/nju-connect

也可以从 Releases 直接下载,Release 页面上列着每个文件的 SHA-256:

  • macOS:nju-connect-<版本>-macos-universal.zip,解压出的 nju-connect.app 放进「应用程序」; 命令行是 nju-connect.app/Contents/Helpers/nju-connect。
  • Linux:nju-connect-<版本>-linux-<x86_64|aarch64>.tar.gz;Windows:nju-connect-<版本>-windows-<x86_64|aarch64>.zip。 把里面的 nju-connect(Windows 上是 nju-connect.exe)放进 PATH。

macOS 应用只有 ad-hoc 签名、未经 Apple 公证。Homebrew 装的直接能打开;直接下载的第一次打开会被 Gatekeeper 拦下, 到「系统设置 › 隐私与安全性」里点「仍要打开」,或者去掉隔离属性:

xattr -dr com.apple.quarantine <解压出来的 nju-connect.app>

Windows 版没有签名,SmartScreen 第一次运行时会警告。各平台能力差异见 docs/architecture.md。也可以从源码构建:

go build -o nju-connect ./cmd/nju-connect

然后:

nju-connect setup     # 引导式设置:后端、网关、账号、密码
nju-connect connect   # 连接;不带命令等同于 connect

aTrust 协议部分使用了 mythologyli/zju-connect 的客户端核心;EasyConnect 部分参考了 lyc8503/NJUConnect 的实现。感谢两个项目的作者和贡献者。 本项目以 AGPL-3.0-or-later(AGPL 第 3 版或任何更新版本)发布,与南京大学、深信服均无隶属关系。

更新

nju-connect update          # 更新到最新版
nju-connect update --check  # 只看有没有新版
nju-connect update --auto off  # 关掉自动检查(on 再打开)
nju-connect --version

Homebrew 装的交给 brew upgrade --cask;直接下载的原地替换自己,.app 里的命令行连同整个 nju-connect.app 一起换, 菜单栏应用重启后是新版。命令在终端里跑完时,知道有新版就在 stderr 提示一行,每天至多查一次。 怎么判断安装方式、会发哪些网络请求见 appkit。

The rest of this README is developer documentation, in English.

CLI setup

nju-connect setup keeps everything in one state directory, os.UserConfigDir()/nju-connect: config.toml for non-secret settings, and the long-lived VPN password and the aTrust session in their own owner-only (0600) files next to it. nju-connect does not use the OS keyring on any platform (see docs/cli-contract.md).

Run in a terminal, nju-connect setup is a guided wizard: it asks for the backend, the gateway, the aTrust sign-in method it discovers, the account and the password, with the saved values as defaults. Re-running it changes only what you answer or pass as flags; the listener, upstream proxy and TLS settings are kept. The first connect without a configuration offers the wizard too, and nju-connect doctor ends with the next command to run.

When the gateway rejects the saved username or password, connect in a terminal asks for the password again (or a new account too), saves it, and signs in again. Without a terminal it stops with a credential_rejected: line. To fix a wrong password or account without connecting:

nju-connect account                  # show the saved account; a menu in a terminal
nju-connect account set-password     # replace only the password
nju-connect account set-username NEW # change the account, forgetting its aTrust session
nju-connect account forget --session # sign in to aTrust from scratch next time

Scripts can pipe the password with setup --password-stdin or account set-password --password-stdin; piped input always gets plain line prompts. TERM=dumb or NJU_CONNECT_ACCESSIBLE=1 does the same on a terminal, and is the fallback for a terminal that shows forms as stacked copies instead of redrawing them in place.

Running nju-connect with no command is equivalent to nju-connect connect and starts the native userspace runtime. Use nju-connect dry-run to validate authentication and the gateway handoff without starting the dataplane.

While a foreground or detached native runtime is active, nju-connect status queries its owner-only local control socket and prints only sanitized state. Use nju-connect status --json for the versioned machine-readable form, and add --watch to stream that snapshot once per second. A stopped runtime is reported explicitly and returns a nonzero status; the command does not infer liveness from a PID file or runtime.log.

Protocol backends

nju-connect separates the application from the campus VPN protocol. The EasyConnect backend is the default. The aTrust backend uses the pinned AGPL-3.0 mythologyli/zju-connect client as its protocol core and runs in the foreground (see docs/architecture.md).

nju-connect backends --json                 # presentation-safe backend catalog
nju-connect configure --backend atrust      # switch non-secret settings only
nju-connect setup --backend atrust --auth-type auth/psw
nju-connect logout                          # forget saved aTrust session state

Both backends share the one CLI-owned SOCKS5 listener (default 127.0.0.1:1081) and the saved VPN password. configure refuses to change the profile while a runtime is active. Set an absolute NJU_CONNECT_CONFIG_DIR to run against an isolated configuration directory.

Campus speed test

nju-connect speedtest measures the pinned NJU campus IPv4 LibreSpeed target. It first tries the system's direct path. If that path is unavailable, an active nju-connect runtime may provide its owner-only loopback SOCKS5 path; otherwise the command asks the user to connect nju-connect and retry. Results always identify the selected path and never infer it from connection state alone.

LibreSpeed is a separate third-party executable. The Cask depends on the librespeed-cli-nju-connect Formula, which builds the pinned upstream source with the SOCKS and structured-progress patches nju-connect needs; nju-connect neither embeds nor downloads it. Without Homebrew, point NJU_CONNECT_LIBRESPEED_CLI at a compatible absolute helper path. JSON and redirected modes fail closed instead of waiting for input. Useful machine interfaces are:

nju-connect speedtest campus --route auto --json
nju-connect speedtest component status --json
nju-connect speedtest last --json

The CLI retains only the latest compact result. The macOS UI separately keeps bounded local graph samples in its own preferences so the inspector can restore the previous curve; the client public IP is not stored.

Releases

Artifact names, the Cask and the update / version commands follow appkit. Pushing a v* tag runs the release workflow: scripts/package.sh builds the universal macOS app on macOS and the Linux and Windows CLI archives on Linux, the workflow publishes them as one GitHub Release, and fills packaging/homebrew/nju-connect.rb with the version and SHA-256 for soundadam/homebrew-tap. The tag is the only version: release builds inject it as main.version. A tag with - is a prerelease and leaves the Cask alone; prerelease tags are -alpha.N, -beta.N or -rc.N, because the app's CFBundleVersion is derived from them (see scripts/package.sh).

Every archive carries README.md, LICENSE, THIRD_PARTY_NOTICES and the linked modules' license texts in third_party_licenses/; the app keeps the same files in Contents/Resources. The app is ad-hoc signed, not notarized.

make package VERSION=1.2.0   # this platform's release artifacts into dist/

Project page

The page at https://soundadam.com/zh/projects/nju-connect/ is written here, in site/: site/content/index.<lang>.md is the copy (schema: .claude/rules/content-schema.md in soundadam/www-src), site/img/ the screenshots it names. soundadam.com imports site/ as a Hugo module and renders it with the site's own layout. A push to main that touches site/ sends project-site to soundadam/www-src, whose build takes this repository's newest main and publishes. A release that changes the version also bumps release.version there. An English page is index.en.md.

docs/index.html only redirects the former GitHub Pages address there.

macOS development

make macos-dev builds the CLI, menu-bar app, and aTrust OAuth helper, signs them with a local Apple Development identity, and runs the app against the freshly built CLI. make macos-preview opens the panel in a window with simulated states; see macos/README.md.

Directories

Path Synopsis
cmd
nju-connect command
internal
app
Package app holds the CLI's use cases.
Package app holds the CLI's use cases.
backend
Package backend defines the stable boundary between the nju-connect application and protocol-specific VPN implementations.
Package backend defines the stable boundary between the nju-connect application and protocol-specific VPN implementations.
backend/atrust
Package atrustbackend is nju-connect's aTrust backend.
Package atrustbackend is nju-connect's aTrust backend.
backend/easyconnect/session
Package nativeapp connects an authenticated gateway boundary to the native userspace runtime.
Package nativeapp connects an authenticated gateway boundary to the native userspace runtime.
core
Package core owns nju-connect's connection state transitions and decisions.
Package core owns nju-connect's connection state transitions and decisions.
credential
Package credential stores long-lived nju-connect authentication material without putting it in command arguments or configuration.
Package credential stores long-lived nju-connect authentication material without putting it in command arguments or configuration.
dial
Package dial owns the outbound network policy shared by gateway authentication and transport.
Package dial owns the outbound network policy shared by gateway authentication and transport.
doctor
Package doctor provides a read-only and secret-free development diagnostic.
Package doctor provides a read-only and secret-free development diagnostic.
owneronly
Package owneronly checks and creates files and directories that only the current user can access.
Package owneronly checks and creates files and directories that only the current user can access.
runtime
Package runtime owns the native L3VPN session lifecycle.
Package runtime owns the native L3VPN session lifecycle.
runtimecontrol
Package runtimecontrol is the private control channel of a running nju-connect runtime: the owner-only Unix socket that serves the sanitized status snapshot and accepts a disconnect request.
Package runtimecontrol is the private control channel of a running nju-connect runtime: the owner-only Unix socket that serves the sanitized status snapshot and accepts a disconnect request.
sessiontoken
Package sessiontoken owns the native gateway token derived from an authenticated EasyConnect gateway session.
Package sessiontoken owns the native gateway token derived from an authenticated EasyConnect gateway session.
traffic
Package traffic records application payload at the local ingress boundary.
Package traffic records application payload at the local ingress boundary.
tui
Package tui answers app.Interaction with terminal forms.
Package tui answers app.Interaction with terminal forms.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL