Inline profile credentials and add install tasks
Hostname is the only input that varies; the credentials are fictional
constants until real auth lands. rake profile becomes a file task and
profile:install / profile:remove wrap the sudo commands, so the resync
loop is one rake command each way.

Assisted-by: GLM 5.2 via pi
change qtqrzrozrrnvorywkruokmkkywokyyyz
commit 136348094113ff99353fa9cab1b4f8a726f60025
author Alpha Chen <alpha@kejadlen.dev>
date
parent qzvwwxwo
diff --git a/Rakefile b/Rakefile
index 6cdb380..174376e 100644
--- a/Rakefile
+++ b/Rakefile
@@ -19,24 +19,30 @@ task :fixtures do
   ExchangeFixtures.record_responses(ProTacts::Web)
 end
 
-desc "Generate carddav.mobileconfig to provision the macOS account"
-task :profile do
+desc "Render the macOS configuration profile (carddav.mobileconfig)"
+task profile: "carddav.mobileconfig"
+
+# Rebuilds when the template changes but not when PRO_TACTS_HOSTNAME does;
+# delete carddav.mobileconfig to force a rerender.
+file "carddav.mobileconfig" => "lib/pro_tacts/profile.rb" do |task|
   require "pro_tacts/profile"
 
-  # Throwaway credentials for the dev loop; real auth is its own backlog task.
-  File.write("carddav.mobileconfig", ProTacts::Profile.render(
-    hostname: ENV.fetch("PRO_TACTS_HOSTNAME"),
-    username: "a@b.com",
-    password: "a"
+  File.write(task.name, ProTacts::Profile.render(
+    hostname: ENV.fetch("PRO_TACTS_HOSTNAME")
   ))
-  identifier = ProTacts::Profile::PAYLOAD_IDENTIFIER
-  puts <<~MESSAGE
-    Wrote carddav.mobileconfig. Install:
-      sudo profiles install -type configuration -path carddav.mobileconfig
-    Remove:
-      sudo profiles remove -identifier #{identifier}
-    Recent macOS may ask you to approve the profile in System Settings → Profiles.
-  MESSAGE
+end
+
+namespace :profile do
+  desc "Install the configuration profile (sudo)"
+  task install: "carddav.mobileconfig" do |task|
+    sh "sudo", "profiles", "install", "-type", "configuration", "-path", task.prerequisites.first
+  end
+
+  desc "Remove the configuration profile (sudo)"
+  task :remove do
+    require "pro_tacts/profile"
+    sh "sudo", "profiles", "remove", "-identifier", ProTacts::Profile::PAYLOAD_IDENTIFIER
+  end
 end
 
 task default: :test
diff --git a/docs/macos-contacts.md b/docs/macos-contacts.md
index 505f514..14eee33 100644
--- a/docs/macos-contacts.md
+++ b/docs/macos-contacts.md
@@ -26,16 +26,12 @@ refusing a redirect) that never reach the server at all.
 
 ## The account setup path
 
-The fastest path is a configuration profile: `rake profile` (with
-`PRO_TACTS_HOSTNAME` set) writes `carddav.mobileconfig`, then
+The fastest path is a configuration profile: `rake profile:install` (with
+`PRO_TACTS_HOSTNAME` set) renders `carddav.mobileconfig` and installs it;
+`rake profile:remove` removes it. Both shell out to sudo.
 
-```sh
-sudo profiles install -type configuration -path carddav.mobileconfig
-sudo profiles remove -identifier dev.kejadlen.pro-tacts.carddav
-```
-
-adds and removes the account. The profile carries the hostname,
-credentials, and SSL — `CardDAVPrincipalURL` is deliberately omitted so the
+The profile carries the hostname, fixed dev credentials, and SSL —
+`CardDAVPrincipalURL` is deliberately omitted so the
 account gets an empty Server Path, exercising discovery. Fixed payload
 identifiers mean a reinstall replaces the account in place, and removal is
 what resets the client's cached discovery results. Recent macOS may stage
diff --git a/lib/pro_tacts/profile.rb b/lib/pro_tacts/profile.rb
index 6076fb9..c689d57 100644
--- a/lib/pro_tacts/profile.rb
+++ b/lib/pro_tacts/profile.rb
@@ -15,12 +15,10 @@ module ProTacts
     TOP_LEVEL_UUID = "6F1E2D3C-4B5A-4E7F-8C9D-0A1B2C3D4E5F"
     PAYLOAD_UUID = "7A2F3E4D-5C6B-4F80-9DAE-1B2C3D4E5F6A"
 
-    def self.render(hostname:, username:, password:)
-      template % {
-        hostname: escape(hostname),
-        username: escape(username),
-        password: escape(password)
-      }
+    # Username and password are a throwaway fictional pair, inlined in the
+    # template. Real auth is its own backlog task.
+    def self.render(hostname:)
+      template % { hostname: escape(hostname) }
     end
 
     def self.template
@@ -49,9 +47,9 @@ module ProTacts
               <key>CardDAVHostName</key>
               <string>%{hostname}</string>
               <key>CardDAVUsername</key>
-              <string>%{username}</string>
+              <string>alpha@example.com</string>
               <key>CardDAVPassword</key>
-              <string>%{password}</string>
+              <string>carddav-dev</string>
               <key>CardDAVUseSSL</key>
               <true/>
             </dict>
diff --git a/test/pro_tacts/test_profile.rb b/test/pro_tacts/test_profile.rb
index 004c68c..bab2de6 100644
--- a/test/pro_tacts/test_profile.rb
+++ b/test/pro_tacts/test_profile.rb
@@ -5,8 +5,8 @@ require_relative "../test_helper"
 require "pro_tacts/profile"
 
 class ProfileTest < Minitest::Test
-  def render(hostname: "example.ts.net", username: "a@b.com", password: "a")
-    ProTacts::Profile.render(hostname:, username:, password:)
+  def render(hostname: "example.ts.net")
+    ProTacts::Profile.render(hostname:)
   end
 
   def test_is_well_formed_xml
@@ -17,12 +17,12 @@ class ProfileTest < Minitest::Test
     assert_includes render, "<string>com.apple.carddav.account</string>"
   end
 
-  def test_embeds_credentials_and_hostname
+  def test_embeds_hostname_and_fixed_dev_credentials
     xml = render
 
     assert_includes xml, "<string>example.ts.net</string>"
-    assert_includes xml, "<string>a@b.com</string>"
-    assert_includes xml, "<string>a</string>"
+    assert_includes xml, "<string>alpha@example.com</string>"
+    assert_includes xml, "<string>carddav-dev</string>"
   end
 
   def test_enables_ssl