Problem
Users who previously installed Freenet via the old install.sh path on macOS have:
- Legacy CLI binary at
/usr/local/bin/freenet (or ~/.local/bin/freenet)
- Legacy launchd agent
~/Library/LaunchAgents/org.freenet.node.plist that auto-starts the legacy binary on login
If such a user downloads the new signed DMG and installs Freenet.app, the current DMG code (landed in #3928 and follow-ups) only:
- Detects
org.freenet.node.plist via legacy_launchd_agent_present() and logs a warning with manual cleanup instructions
- Treats "legacy present" as "already enabled" for the Launch-at-Login decision, so it doesn't write a duplicate plist
It does NOT remove the legacy plist, unload its launchd agent, or remove the legacy CLI. Consequences:
- On next login, launchd auto-starts the legacy (old-version) binary, binding port 7509. The new DMG's Freenet.app has to fight for the port if the user manually opens it.
kill_stale_freenet_processes in the DMG wrapper pkills the legacy backend on startup, triggering a flap where the legacy launchd agent respawns it — then the new wrapper's backend loses the port race and exits 43.
Existing users are largely fine because the legacy install has its own auto-update mechanism and will keep picking up new versions. But anyone who WANTS to switch to the DMG path (e.g., because they want the tray and Launch-at-Login UX, or we promote the DMG as the preferred install) has to manually clean up first, per the warning log line.
Proposed solution
On first launch of DMG-installed Freenet.app (gated on the first-run marker), if legacy_launchd_agent_present() returns true, auto-migrate:
launchctl bootout gui/$UID ~/Library/LaunchAgents/org.freenet.node.plist
rm ~/Library/LaunchAgents/org.freenet.node.plist
rm /usr/local/bin/freenet and/or rm ~/.local/bin/freenet and/or rm ~/bin/freenet (whichever exist and are owned by the user)
- Log each action clearly in the wrapper log
- Proceed with the normal DMG-install first-run flow, which then writes the NEW
org.freenet.Freenet.plist
Silent is appropriate — this matches what a Windows installer would do on upgrade and avoids a mystery dialog for users who don't understand their own install history. Data directories (~/Library/Application Support/freenet/) are shared between old and new installs, so user state is preserved across the migration.
Alternatives considered:
- AppleScript prompt ([Migrate / Skip]): more respectful of user choice but adds GUI dependency and a confusing dialog for users who don't know they have two installs.
- Documentation only: the current approach plus a quickstart-page note. Relies on users reading. Acceptable for v0.2.49 release but should not be the long-term answer.
Implementation notes
- Pure-function extraction per
.claude/rules/deployment.md: legacy_install_migration_plan(exe_exists: bool, plist_exists: bool) -> MigrationAction → exhaustive cases, unit-testable on Linux CI.
- Scope to macOS only.
- Needs to handle
sudo permission if the legacy CLI lives at /usr/local/bin/freenet and isn't user-owned. Fall back to logging "please manually remove X" if rm fails, rather than failing the whole migration.
- Write a regression test that reproduces the legacy-install scenario with a temp
$HOME and asserts the migration cleans up correctly.
Priority
Medium. New-user DMG install path is unaffected (no legacy present). Existing install.sh users keep working via legacy auto-update. Only actively affects users who try to switch install mechanisms, which is a minority. Worth doing before we promote the DMG as the default install in the quickstart, but not urgent enough to block v0.2.49.
Context
Discussed in the lead-up to v0.2.49 (Freenet.org/quickstart will point at the DMG download once the dup-tray fix from #3939 lands). Ian confirmed existing users don't strictly need to migrate because the legacy install already auto-updates, so this is quality-of-life for deliberate migrators rather than a correctness blocker.
[AI-assisted - Claude]
Problem
Users who previously installed Freenet via the old
install.shpath on macOS have:/usr/local/bin/freenet(or~/.local/bin/freenet)~/Library/LaunchAgents/org.freenet.node.plistthat auto-starts the legacy binary on loginIf such a user downloads the new signed DMG and installs Freenet.app, the current DMG code (landed in #3928 and follow-ups) only:
org.freenet.node.plistvialegacy_launchd_agent_present()and logs a warning with manual cleanup instructionsIt does NOT remove the legacy plist, unload its launchd agent, or remove the legacy CLI. Consequences:
kill_stale_freenet_processesin the DMG wrapperpkills the legacy backend on startup, triggering a flap where the legacy launchd agent respawns it — then the new wrapper's backend loses the port race and exits 43.Existing users are largely fine because the legacy install has its own auto-update mechanism and will keep picking up new versions. But anyone who WANTS to switch to the DMG path (e.g., because they want the tray and Launch-at-Login UX, or we promote the DMG as the preferred install) has to manually clean up first, per the warning log line.
Proposed solution
On first launch of DMG-installed Freenet.app (gated on the first-run marker), if
legacy_launchd_agent_present()returns true, auto-migrate:launchctl bootout gui/$UID ~/Library/LaunchAgents/org.freenet.node.plistrm ~/Library/LaunchAgents/org.freenet.node.plistrm /usr/local/bin/freenetand/orrm ~/.local/bin/freenetand/orrm ~/bin/freenet(whichever exist and are owned by the user)org.freenet.Freenet.plistSilent is appropriate — this matches what a Windows installer would do on upgrade and avoids a mystery dialog for users who don't understand their own install history. Data directories (
~/Library/Application Support/freenet/) are shared between old and new installs, so user state is preserved across the migration.Alternatives considered:
Implementation notes
.claude/rules/deployment.md:legacy_install_migration_plan(exe_exists: bool, plist_exists: bool) -> MigrationAction→ exhaustive cases, unit-testable on Linux CI.sudopermission if the legacy CLI lives at/usr/local/bin/freenetand isn't user-owned. Fall back to logging "please manually remove X" if rm fails, rather than failing the whole migration.$HOMEand asserts the migration cleans up correctly.Priority
Medium. New-user DMG install path is unaffected (no legacy present). Existing install.sh users keep working via legacy auto-update. Only actively affects users who try to switch install mechanisms, which is a minority. Worth doing before we promote the DMG as the default install in the quickstart, but not urgent enough to block v0.2.49.
Context
Discussed in the lead-up to v0.2.49 (Freenet.org/quickstart will point at the DMG download once the dup-tray fix from #3939 lands). Ian confirmed existing users don't strictly need to migrate because the legacy install already auto-updates, so this is quality-of-life for deliberate migrators rather than a correctness blocker.
[AI-assisted - Claude]