···4455 Supports two activation methods:
66 - Socket mode: Communicates with sower-activator daemon via Unix socket
77- - CLI mode: Invokes sower-activator binary directly (with sudo)
77+ - CLI mode: Invokes sower-activator binary directly (with sudo for NixOS)
8899- The `activate/3` function automatically tries socket first, then falls back to CLI.
99+ ## Activation Path Selection
1010+1111+ The `activate/3` function selects the activation method based on type and context:
1212+1313+ 1. For **home-manager** with username tag matching the current user:
1414+ Uses CLI activation directly (no sudo, no socket) - enables self-managed
1515+ home-manager configurations without requiring a privileged daemon.
1616+1717+ 2. For **NixOS** or other types:
1818+ Uses socket activation when available, falls back to CLI with sudo.
1919+2020+ 3. For **home-manager** with mismatched username:
2121+ Uses socket activation when available, otherwise returns `{:error, :username_mismatch}`.
1022 """
11231224 use TypedStruct
···5466 """
5567 def activate(type, path, opts \\ []) do
5668 socket_path = Keyword.get(opts, :socket_path, @default_socket_path)
6969+ tags = Keyword.get(opts, :tags, [])
57705858- if socket_available?(socket_path) do
5959- activate_via_socket(type, path, opts)
6060- else
6161- Logger.debug("Socket not available, falling back to CLI activation")
7171+ if type == "home-manager" and username_matches_current_user?(tags) do
7272+ Logger.debug("Home-manager username matches current user, using direct CLI activation")
6273 activate_via_cli(type, path, opts)
7474+ else
7575+ if socket_available?(socket_path) do
7676+ activate_via_socket(type, path, opts)
7777+ else
7878+ Logger.debug("Socket not available, falling back to CLI activation")
7979+ activate_via_cli(type, path, opts)
8080+ end
6381 end
6482 end
6583···204222 def socket_available?(socket_path \\ @default_socket_path) do
205223 File.exists?(socket_path)
206224 end
225225+226226+ @doc """
227227+ Check if the username tag matches the current user.
228228+229229+ Returns `true` if a username tag exists and matches `System.get_env("USER")`.
230230+ Returns `false` if no username tag found or it doesn't match.
231231+ """
232232+ def username_matches_current_user?(tags) when is_list(tags) do
233233+ current_user = System.get_env("USER")
234234+235235+ case Enum.find(tags, &(&1.key == "username")) do
236236+ %{value: ^current_user} -> true
237237+ _ -> false
238238+ end
239239+ end
240240+241241+ def username_matches_current_user?(_tags), do: false
207242208243 # Private functions - Socket communication
209244