diff --git a/AGENTS.md b/AGENTS.md index d3e2348..ca0f583 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,7 +61,7 @@ Ansible-driven personal infrastructure repo for Fedora and Void desktops, Fedora `ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff` - Atlas rootless Gitea staging (does not start Gitea): `ansible-playbook ansible/site.yml --limit atlas --tags gitea --check --diff` - - Atlas iCloudPD storage and inactive Quadlet (does not start it): + - Atlas iCloudPD storage and boot-started Quadlet: `ansible-playbook ansible/site.yml --limit atlas --tags icloudpd --check --diff` - Atlas explicit Gitea host-owner migration (live outage; never a normal run): `ansible-playbook ansible/site.yml --limit atlas --tags gitea_owner_migration -e atlas_gitea_owner_migration=true` @@ -375,8 +375,10 @@ successfully. The first monthly scrub remains a runtime check. - [x] Deploy the declared Atlas iCloudPD state dataset and inactive rootless `admin` Quadlet. Photos belong under `/zpool/archive/Pictures/iCloudPD`; private config/MFA state belongs in `zpool/services/data/icloudpd`. Photobook remains reserved for Immich. Ansible now renders - `icloudpd.conf` with the Apple ID from the existing Vault key, but does not store the password, - manage MFA, or enable automatic startup. The isolated no-network layout test is documented in + `icloudpd.conf` with the Apple ID from the existing Vault key, but does not store the password + or manage MFA. Automatic startup was approved on 2026-10-03; the Quadlet now + uses `WantedBy=default.target` and Ansible keeps the service running. + The isolated no-network layout test is documented in `docs/atlas-icloudpd-migration.md`. On 2026-10-02 Atlas deployment and a second idempotent run passed; no app config existed at deployment. A manual first start on 2026-10-02 generated `icloudpd.conf`; an Ansible run then replaced it with a private mode-0600 Vault-backed template diff --git a/ansible/roles/profile_atlas/tasks/icloudpd.yml b/ansible/roles/profile_atlas/tasks/icloudpd.yml index 9695bd6..632fb97 100644 --- a/ansible/roles/profile_atlas/tasks/icloudpd.yml +++ b/ansible/roles/profile_atlas/tasks/icloudpd.yml @@ -13,7 +13,7 @@ - atlas_icloudpd_image is search('@sha256:[0-9a-f]{64}$') fail_msg: Verify the fixed, separate Atlas iCloudPD photo and state paths. -- name: Declare inactive rootless Atlas iCloudPD storage and Quadlet +- name: Declare rootless Atlas iCloudPD storage and boot-started Quadlet tags: [atlas, icloudpd] block: - name: Inspect the existing Archive and application-data datasets @@ -162,4 +162,27 @@ owner: "{{ atlas_admin_username }}" group: "{{ atlas_admin_group }}" mode: "0644" - notify: Reload Atlas admin user manager + register: atlas_icloudpd_quadlet + + - name: Reload the Atlas admin user manager after iCloudPD Quadlet changes + become_user: "{{ atlas_admin_username }}" + ansible.builtin.systemd: + scope: user + daemon_reload: true + environment: + XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}" + DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_admin_uid }}/bus" + when: + - atlas_icloudpd_quadlet.changed + - not ansible_check_mode + + - name: Keep the rootless Atlas iCloudPD service running + become_user: "{{ atlas_admin_username }}" + ansible.builtin.systemd: + name: atlas-icloudpd.service + scope: user + state: started + environment: + XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}" + DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_admin_uid }}/bus" + when: not ansible_check_mode diff --git a/ansible/roles/profile_atlas/tasks/main.yml b/ansible/roles/profile_atlas/tasks/main.yml index 044b349..259059b 100644 --- a/ansible/roles/profile_atlas/tasks/main.yml +++ b/ansible/roles/profile_atlas/tasks/main.yml @@ -20,7 +20,7 @@ - name: Import staged Atlas rootless Gitea tasks ansible.builtin.import_tasks: gitea.yml -- name: Import Atlas iCloudPD storage and inactive Quadlet tasks +- name: Import Atlas iCloudPD storage and boot-started Quadlet tasks ansible.builtin.import_tasks: icloudpd.yml - name: Import explicit Atlas Gitea restore rehearsal tasks diff --git a/ansible/roles/profile_atlas/templates/atlas-icloudpd.container.j2 b/ansible/roles/profile_atlas/templates/atlas-icloudpd.container.j2 index 635c9b2..5c6f06b 100644 --- a/ansible/roles/profile_atlas/templates/atlas-icloudpd.container.j2 +++ b/ansible/roles/profile_atlas/templates/atlas-icloudpd.container.j2 @@ -1,4 +1,4 @@ -# Managed by Ansible. No install target or automatic start. +# Managed by Ansible. Start automatically with the lingering admin user manager. [Unit] Description=Atlas rootless iCloud Photos Downloader RequiresMountsFor={{ atlas_icloudpd_state_dir }} {{ atlas_icloudpd_photos_dir }} @@ -20,3 +20,6 @@ NoNewPrivileges=true Restart=on-failure RestartSec=300 TimeoutStartSec=900 + +[Install] +WantedBy=default.target diff --git a/docs/atlas-icloudpd-migration.md b/docs/atlas-icloudpd-migration.md index 02b2d94..276874f 100644 --- a/docs/atlas-icloudpd-migration.md +++ b/docs/atlas-icloudpd-migration.md @@ -55,11 +55,12 @@ path, user/UID, and folder format as the bind mounts. References: The photo subtree receives a managed marker and the image's `.mounted` file. An existing unmarked path is refused rather than taken over. The existing -Pictures tree is not chowned or emptied. The Quadlet has no `[Install]` -section, so Ansible does not start or enable it. Ansible renders a mode-0600 +Pictures tree is not chowned or emptied. The Quadlet now has `[Install]` with +`WantedBy=default.target`, so the lingering admin user manager starts it at boot. +Ansible keeps the service running. Ansible renders a mode-0600 `icloudpd.conf` with `no_log` and no diff, but does not pull the image, -initialize MFA, or run a cutover task. The service was started manually and -will not start automatically after reboot under this design. +initialize MFA, or run a cutover task. Boot startup was approved on 2026-10-03 +after a reboot left the previously manual-started service inactive. The previous gated check-mode tests and isolated Quadlet-generator test proved only the proposed layout; they predate the simplified declarative role. They @@ -110,8 +111,8 @@ completed backup or restore of iCloudPD data**, which did not exist at the time. on first start. Ansible replaced that default file with a private template using the Apple ID already in Vault. The operator initialized password and MFA interactively; never put credentials or codes in the repository, - chat, or Ansible extra-vars. The Quadlet has no automatic boot start; - enablement requires a separate deliberate design change. + chat, or Ansible extra-vars. Automatic boot startup was separately approved + on 2026-10-03; this does not change the interactive MFA procedure. - Initial ingestion completed on 2026-10-03. Still check folder structure, ownership, SELinux and SMB access, no unintended deletions, the next daily cycle, completed Borg and USB versions, and isolated restore of photos and @@ -161,3 +162,12 @@ is a filesystem file count, not a count of distinct iCloud assets. A later read-only check found the service still active. This closes initial authentication and ingestion only: a subsequent daily cycle and end-to-end recovery of the new photos and private state remain untested. + +On 2026-10-03 Atlas rebooted at 10:17 CEST; iCloudPD stayed inactive because +its Quadlet had no install target. A manual start restored the running service +and the application began listing iCloud files. The operator then approved +persistent boot startup. The managed Quadlet now declares +`WantedBy=default.target`; the live generator created +`default.target.wants/atlas-icloudpd.service`, admin has `Linger=yes`, and the +service remained active with zero restarts. No NAS reboot was performed to +test this change; actual post-reboot startup remains untested.