Skip to content

Bare wanctl prints help; wanctl start enrolls the device - #69

Merged
Daily-AC merged 1 commit into
mainfrom
feat/bare-wanctl-prints-help
Sep 9, 2026
Merged

Bare wanctl prints help; wanctl start enrolls the device#69
Daily-AC merged 1 commit into
mainfrom
feat/bare-wanctl-prints-help

Conversation

@Daily-AC

@Daily-AC Daily-AC commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Summary

Bare wanctl stops being an action. It prints the help plus one line of local
status and returns; wanctl start does the enrolling.

  • main.go — the no-argument path prints usage and localStatusLine(). It no
    longer runs the first-run relay gate, so it asks nothing and writes nothing.
    The up case, cmdUp and the now-unused runRelayCommand are gone, and up
    is out of relayCommands.
  • daemon.gocmdStart(ctx) logs in when there is no token, via the
    enrollForStart variable so a test can drive the rest of start without a
    portal. localStatusLine() is the new status line.
  • internal/config/lock.goAgentRunning opens agent.lock without
    O_CREATE. Asking whether an agent runs must not create the file that records
    that one ever did; no lock file also answers the question.
  • Usage text, and internal/config/store.go's reference to the token "saved by
    wanctl up".

Root cause

main.go dispatched len(os.Args) < 2 to cmdUp, which enrolled if there was
no token and then called cmdStart. Reading the help and onboarding a device
were the same keystroke. cmdStart reinforced it by refusing without a token
and telling the user to "先运行 wanctl(无参)完成登录授权", so the documented
way to enroll was the same command someone would type to look around.

wanctl login was already correct — it takes a credential and starts nothing —
so only the device half moved.

up is removed rather than kept as an alias. I checked every caller the brief
named: grep -rn cmdUp finds only the bare path; the Android app runs
Wanctl.run(…, "login", "--code", …) and AgentService execs agent; neither
scripts/install.sh.in nor scripts/install.ps1.in invokes wanctl at all after
installing; cmdSupervise runs agent --managed; service.go writes units that
run agent.

Doc files touched

For the portal re-sync after deploy:

docs/portal/quickstart__enroll-device.md
docs/portal/quickstart__enroll-device.en.md
docs/portal/quickstart__windows-install.md
docs/portal/quickstart__windows-install.en.md
docs/self-hosting.md
docs/self-hosting.zh.md
internal/relay/skill.md
internal/portal/changelog/v0.8.1.md   (new)

README.md and docs/android*.md already said wanctl start where it matters
and needed no change; their remaining bare-wanctl mentions are about PATH
resolution, not enrollment. firstrun.go never told anyone to run bare wanctl.

Test plan

$ go test . -run 'BareInvocation|UnknownSubcommand|Start|Login|LocalStatusLine' -v
--- PASS: TestBareInvocationPrintsHelpAndTouchesNothing (1.20s)
--- PASS: TestUnknownSubcommandStillFails (0.87s)
--- PASS: TestStartEnrollsWhenThereIsNoToken (0.00s)
--- PASS: TestStartWithAnAgentAlreadyRunningDoesNothing (0.00s)
--- PASS: TestLoginNeverRecordsAnAgent (0.00s)
--- PASS: TestLocalStatusLineReportsBothHalves (0.00s)

The bare-invocation test builds the binary and runs it against a temp config
directory with no relay configured, then asserts exit 0, the help and the status
line on stdout, and that none of token, agent.pid, agent.lock, cert.pem,
key.pem or device_id was created. Running with no relay is deliberate: it is
what proves the first-run question is not reached.

TestStartEnrollsWhenThereIsNoToken stubs the login to fail and asserts start
attempted it exactly once, returned that failure, and recorded no agent.
TestStartWithAnAgentAlreadyRunningDoesNothing holds the lock and asserts the
login is not re-run. TestLoginNeverRecordsAnAgent asserts login leaves no
agent.pid and no agent.lock.

Full gate clean: gofmt -l ., go vet ./..., go test ./...,
go test -tags lark ./..., go vet -tags lark ./..., Windows and Android
builds, GOOS=linux go vet ./....

Migration

Devices: wanctl start. Controllers: wanctl login. Agents already running are
unaffected. internal/portal/changelog/v0.8.1.md carries the same note for the
portal's release sheet.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SpPAyRQzTAw7MbmJqZdm7e

Running `wanctl` with no arguments logged in and then registered the
machine as a controlled device. Someone on a controller-only box who
typed it to see what the command does got that box enrolled -- which is
not what anyone means by running a program to read its help.

Bare `wanctl` now prints the usage text plus one line of local status
and returns. It reads no relay setting, asks no first-run question, and
writes nothing, so a fresh config directory is left exactly as found.
The liveness probe behind that status line no longer creates agent.lock
either: a directory with no lock file has never run an agent, which
answers the question without touching the disk.

`wanctl start` absorbs the login step, so onboarding a device stays one
command. `wanctl login` is unchanged and still never starts an agent.
`wanctl up` is removed rather than aliased: nothing outside the bare
path used it -- not the installers, not the Android app, not the Windows
supervisor or the service units.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SpPAyRQzTAw7MbmJqZdm7e
@Daily-AC
Daily-AC merged commit c554aa1 into main Sep 9, 2026
4 checks passed
@Daily-AC
Daily-AC deleted the feat/bare-wanctl-prints-help branch September 9, 2026 08:38
Daily-AC added a commit that referenced this pull request Sep 9, 2026
`wanctl update` stopped the agent, swapped the binary and started a new
one while the old process was still shutting down. Terminating is
asynchronous and cmdStop returned as soon as the signal was delivered,
so the new agent hit a config-dir lock the dying one still held, exited,
and left the machine with nothing running -- after the parent had
already printed "✓ 服务已转后台". A device dropped off the relay for
fifty minutes that way.

cmdStop now waits for the lock to come free, up to ten seconds, and
fails loudly rather than letting the caller replace the binary and start
a second agent behind a first that will not leave.

The agent gained the other half: it retries a held lock for up to five
seconds instead of exiting at once, because the process that started it
has already reported success. Its message no longer blames itself
either. `wanctl start` records the child's pid before the child locks
anything, so an agent that lost the race read its own number out of
agent.pid and reported "another agent is already running (pid <itself>)",
sending the reader after the process that was printing the message.

The auto-update paths were checked and need no change: on Unix the lock
is closed before syscall.Exec and re-acquired by the new image in the
same process, on Windows it is released before the successor is spawned,
and the Scheduled Task supervisor waits for the child to exit before
running the next one.

Also the status hint #69 missed: "未运行" now says `wanctl start`.


Claude-Session: https://claude.ai/code/session_01SpPAyRQzTAw7MbmJqZdm7e

Co-authored-by: 张以琳 <zhangyilin@thunder.com.cn>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant