Skip to content

macOS: auto-migrate install.sh legacy install on first DMG launch #3943

Description

@sanity

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:

  1. Detects org.freenet.node.plist via legacy_launchd_agent_present() and logs a warning with manual cleanup instructions
  2. 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:

  1. launchctl bootout gui/$UID ~/Library/LaunchAgents/org.freenet.node.plist
  2. rm ~/Library/LaunchAgents/org.freenet.node.plist
  3. rm /usr/local/bin/freenet and/or rm ~/.local/bin/freenet and/or rm ~/bin/freenet (whichever exist and are owned by the user)
  4. Log each action clearly in the wrapper log
  5. 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]

Metadata

Metadata

Assignees

Labels

A-developer-xpArea: developer experienceE-mediumExperience needed to fix/implement: Medium / intermediateP-mediumMedium priorityT-enhancementType: Improvement to existing functionality

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions