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

nju-connect

module
v1.1.0-alpha.2 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2026 License: AGPL-3.0

README

nju-connect

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

项目主页:https://soundadam.github.io/nju-connect/ · 下载:Releases

安装

macOS(包含菜单栏应用和 nju-connect 命令):

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

应用未经 Apple 公证(ad-hoc 签名),第一次打开会被 Gatekeeper 拦截。确认信任这份构建后, 在「系统设置 › 隐私与安全性」里点「仍要打开」,或运行:

xattr -dr com.apple.quarantine /Applications/nju-connect.app

Linux / Windows 从源码构建(Go 1.25+):

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 发布,与南京大学、深信服均无隶属关系。

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

CLI setup and migration

nju-connect setup stores non-secret configuration in the operating system's user configuration directory. The long-lived VPN password and the aTrust session are kept in the system keyring under service com.soundadam.nju-connect: the login Keychain on macOS, the Secret Service on Linux, or the Windows Credential Manager. They are never written to the TOML configuration. On a host without a keyring, such as a headless Linux box over SSH, add credential_store = "file" to config.toml to keep them in owner-only files instead.

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.

Pre-release worktree state can be imported explicitly without overwriting an existing destination:

nju-connect migrate --from /path/to/old/nju-connect-worktree

The migration verifies and copies the old state and preserves the source for manual rollback or deletion after verification.

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 remains a separate third-party executable. On macOS, the preview Cask depends on the librespeed-cli-nju-connect Formula, which builds the pinned upstream source with the explicit SOCKS and structured-progress patches required by nju-connect. The App neither embeds nor downloads this helper. Linux packaging remains a separate decision; developers may point to a compatible absolute helper path with NJU_CONNECT_LIBRESPEED_CLI. 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. Developer ID signing, notarization, and a real bandwidth test remain explicit release/operator gates.

CLI release packaging

From a clean worktree, package the two supported macOS CLI architectures with an injected semantic version and checksums:

make cli-release VERSION=v1.0.0

nju-connect is licensed under AGPL-3.0; see LICENSE. The archives include LICENSE, THIRD_PARTY_NOTICES, and the linked modules' license texts. Signing, notarization, tag creation, upload, and publication remain separate release gates.

macOS development first run

For an unsigned or quarantined development copy only:

xattr -d com.apple.quarantine /path/to/nju-connect

This is a local testing workaround, not a release installation step. Release artifacts should be signed and notarized.

The current Homebrew Cask packages the macOS menu-bar client plus the native universal CLI. The menu bar saves credentials through the CLI into the Keychain, starts and stops the real background userspace runtime, submits one-time codes through a private stdin pipe, and streams sanitized runtime state while the panel is open. Build it with make package-macos VERSION=X.Y.Z; the Cask must continue to disclose that ad-hoc signing is not Apple notarization.

For an explicitly local, dirty-tree preview, use make package-macos-local VERSION=X.Y.Z. It records source_dirty=true, writes a file:// Cask into the installed soundadam/local tap checkout (falling back to ~/workspaces/soundadam/homebrew-local when the tap is not installed), and never publishes an artifact.

Versioning and local iteration

nju-connect follows the sing-box prerelease sequence. Git tags add a leading v, while the version embedded in the app and CLI does not:

v1.1.0-alpha.1 -> v1.1.0-beta.1 -> v1.1.0-rc.1 -> v1.1.0

Repeated builds in one channel increment its sequence number. By default, a local update from installed stable version 1.0.0 starts the next feature train as 1.1.0-alpha.1, and the next local update becomes 1.1.0-alpha.2. Stable maintenance releases remain on the patch line, so the stable successor to 1.0.0 is 1.0.1:

make local-update
make local-update CHANNEL=beta
make local-update CHANNEL=rc
make local-update CHANNEL=stable

local-update resolves the active tap with brew --repo soundadam/local. It runs the release checks, builds the universal app, updates and commits the local Cask, reinstalls it, removes quarantine only from the installed development preview, and verifies the installed app, embedded CLI, code signature, and helper hash. Set PUSH_TAP=1 to push the resulting private-tap commit. Use INSTALL_UPDATE=0 when only packaging and committing the Cask is desired.

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.

Local Casks use file:// artifacts and are not public releases. A public release must start from a clean tagged commit, use immutable uploaded assets, verify the downloaded SHA-256, and satisfy signing and notarization gates.

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 future gateway authentication and transport implementations.
Package dial owns the outbound network policy shared by future gateway authentication and transport implementations.
doctor
Package doctor provides a read-only and secret-free development diagnostic.
Package doctor provides a read-only and secret-free development diagnostic.
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