Enable boot startup for Atlas iCloudPD

This commit is contained in:
Fabio Scotto di Santolo
2026-10-03 13:59:05 +02:00
parent 755f24bc72
commit 2dfe766b7b
5 changed files with 51 additions and 13 deletions

View File

@@ -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` `ansible-playbook ansible/site.yml --limit atlas --tags storage,sharing,containers --check --diff`
- Atlas rootless Gitea staging (does not start Gitea): - Atlas rootless Gitea staging (does not start Gitea):
`ansible-playbook ansible/site.yml --limit atlas --tags gitea --check --diff` `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` `ansible-playbook ansible/site.yml --limit atlas --tags icloudpd --check --diff`
- Atlas explicit Gitea host-owner migration (live outage; never a normal run): - 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` `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. - [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 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 `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, `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 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 `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 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 `icloudpd.conf`; an Ansible run then replaced it with a private mode-0600 Vault-backed template

View File

@@ -13,7 +13,7 @@
- atlas_icloudpd_image is search('@sha256:[0-9a-f]{64}$') - atlas_icloudpd_image is search('@sha256:[0-9a-f]{64}$')
fail_msg: Verify the fixed, separate Atlas iCloudPD photo and state paths. 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] tags: [atlas, icloudpd]
block: block:
- name: Inspect the existing Archive and application-data datasets - name: Inspect the existing Archive and application-data datasets
@@ -162,4 +162,27 @@
owner: "{{ atlas_admin_username }}" owner: "{{ atlas_admin_username }}"
group: "{{ atlas_admin_group }}" group: "{{ atlas_admin_group }}"
mode: "0644" 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

View File

@@ -20,7 +20,7 @@
- name: Import staged Atlas rootless Gitea tasks - name: Import staged Atlas rootless Gitea tasks
ansible.builtin.import_tasks: gitea.yml 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 ansible.builtin.import_tasks: icloudpd.yml
- name: Import explicit Atlas Gitea restore rehearsal tasks - name: Import explicit Atlas Gitea restore rehearsal tasks

View File

@@ -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] [Unit]
Description=Atlas rootless iCloud Photos Downloader Description=Atlas rootless iCloud Photos Downloader
RequiresMountsFor={{ atlas_icloudpd_state_dir }} {{ atlas_icloudpd_photos_dir }} RequiresMountsFor={{ atlas_icloudpd_state_dir }} {{ atlas_icloudpd_photos_dir }}
@@ -20,3 +20,6 @@ NoNewPrivileges=true
Restart=on-failure Restart=on-failure
RestartSec=300 RestartSec=300
TimeoutStartSec=900 TimeoutStartSec=900
[Install]
WantedBy=default.target

View File

@@ -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. 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 An existing unmarked path is refused rather than taken over. The existing
Pictures tree is not chowned or emptied. The Quadlet has no `[Install]` Pictures tree is not chowned or emptied. The Quadlet now has `[Install]` with
section, so Ansible does not start or enable it. Ansible renders a mode-0600 `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, `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 initialize MFA, or run a cutover task. Boot startup was approved on 2026-10-03
will not start automatically after reboot under this design. after a reboot left the previously manual-started service inactive.
The previous gated check-mode tests and isolated Quadlet-generator test proved The previous gated check-mode tests and isolated Quadlet-generator test proved
only the proposed layout; they predate the simplified declarative role. They 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 on first start. Ansible replaced that default file with a private template
using the Apple ID already in Vault. The operator initialized password using the Apple ID already in Vault. The operator initialized password
and MFA interactively; never put credentials or codes in the repository, and MFA interactively; never put credentials or codes in the repository,
chat, or Ansible extra-vars. The Quadlet has no automatic boot start; chat, or Ansible extra-vars. Automatic boot startup was separately approved
enablement requires a separate deliberate design change. on 2026-10-03; this does not change the interactive MFA procedure.
- Initial ingestion completed on 2026-10-03. Still check folder structure, - Initial ingestion completed on 2026-10-03. Still check folder structure,
ownership, SELinux and SMB access, no unintended deletions, the next daily ownership, SELinux and SMB access, no unintended deletions, the next daily
cycle, completed Borg and USB versions, and isolated restore of photos and 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 read-only check found the service still active. This closes initial
authentication and ingestion only: a subsequent daily cycle and end-to-end authentication and ingestion only: a subsequent daily cycle and end-to-end
recovery of the new photos and private state remain untested. 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.