From 4bd6aafb53740a6dd324bae7e1067a5db1c82b3d Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Sat, 3 Oct 2026 09:59:59 +0200 Subject: [PATCH] Feature/atlas icloudpd migration (#14) * Design gated Atlas iCloudPD migration target * Target Atlas iCloudPD photos to Photobook * Record isolated iCloudPD Photobook ACL validation * Record Aegis iCloudPD source audit gap * Verify iCloudPD backup source scope and Borg access * Record Atlas iCloudPD deployment gate checks * Pin iCloudPD photo file and directory modes * Validate inactive iCloudPD Quadlet on Atlas generator * Keep iCloudPD in Archive and reserve Photobook for Immich * Prepare guarded Aegis iCloudPD retirement * Declare inactive Atlas iCloudPD storage and Quadlet * Retire Aegis iCloudPD from desired state * Clear retired Aegis iCloudPD failed-unit state * Remove completed iCloudPD retirement tasks from Aegis * Record initial Atlas iCloudPD service start * Manage Atlas iCloudPD config from Vault * Fix Atlas iCloudPD traceroute startup and config drift * Use Atlas Vault key for iCloudPD Apple ID * Add HEIC decoding to Fedora desktops * Record completed iCloudPD ingestion and remaining recovery checks --- AGENTS.md | 41 ++++- README.it.md | 15 +- README.md | 22 +-- ansible/inventory/host_vars/aegis.yml | 2 - ansible/inventory/host_vars/atlas.yml | 4 +- ansible/roles/packages_fedora/tasks/main.yml | 30 ++++ ansible/roles/profile_aegis/defaults/main.yml | 4 - ansible/roles/profile_aegis/handlers/main.yml | 3 +- ansible/roles/profile_aegis/tasks/main.yml | 20 +-- .../templates/icloudpd.container.j2 | 20 --- ansible/roles/profile_atlas/defaults/main.yml | 11 ++ ansible/roles/profile_atlas/handlers/main.yml | 10 ++ .../roles/profile_atlas/tasks/icloudpd.yml | 165 ++++++++++++++++++ ansible/roles/profile_atlas/tasks/main.yml | 3 + .../templates/atlas-icloudpd.conf.j2 | 14 ++ .../templates/atlas-icloudpd.container.j2 | 22 +++ docs/atlas-icloudpd-migration.md | 163 +++++++++++++++++ secrets/vault.yml | 152 ++++++++-------- secrets/vault.yml.example | 2 +- 19 files changed, 550 insertions(+), 153 deletions(-) delete mode 100644 ansible/roles/profile_aegis/templates/icloudpd.container.j2 create mode 100644 ansible/roles/profile_atlas/tasks/icloudpd.yml create mode 100644 ansible/roles/profile_atlas/templates/atlas-icloudpd.conf.j2 create mode 100644 ansible/roles/profile_atlas/templates/atlas-icloudpd.container.j2 create mode 100644 docs/atlas-icloudpd-migration.md diff --git a/AGENTS.md b/AGENTS.md index 66a10f3..4f6f491 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,6 +59,8 @@ 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): + `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` - Atlas explicit isolated Gitea restore rehearsal (not part of normal runs): @@ -362,11 +364,36 @@ successfully. The first monthly scrub remains a runtime check. container paths, and the required Vault database secret. ### Priority 4 - Optional workflows -- [ ] After data protection is validated, move iCloudPD photo ingestion from Aegis to Atlas as a - temporary service until Uranus is ready. Plan to store photos in `/zpool/archive/Pictures` and - persistent application/MFA state outside `Archive`; validate permissions, SELinux, backups and - recovery before cutover. Keep the current Aegis service and Photobook NFS export unchanged until - the Atlas workflow is tested, then retire them explicitly if no longer needed. +- [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 + `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 + and an idempotent second run. The image later expanded the config, so Ansible now seeds it + only when absent and maintains the declared fields. Its launcher requires `traceroute`; the + rootless Quadlet grants only `NET_RAW`, tested in isolation and after restart. The service + was subsequently initialized interactively; initial ingestion is tracked below. +- [x] Retire Aegis iCloudPD completely. The operator authorized deleting its Quadlet, + `/var/lib/icloudpd` data, and MFA state despite an unaudited container overlay. After two + interactive-sudo runs on 2026-10-02, the unit is `not-found`/`inactive`, the Quadlet and state + directory are absent, and AdGuard remains active. The temporary retirement tasks have since + been removed from the Aegis role; it no longer manages iCloudPD. +- [x] Validate Atlas iCloudPD authentication and initial ingestion. On 2026-10-03 the active + rootless service logged `All photos and videos have been downloaded` at 02:16 and reported + completion for the user. The destination held 11,658 files (86,020,430,015 bytes); the preceding 24h + logs showed download activity without authentication failures or errors. A later read-only check + found the service still active. This confirms the initial download, not the next daily cycle. +- [x] Declare HEIC decoding for Fedora graphical desktops without converting the originals on Atlas. + The Fedora role installs RPM Fusion Free with a pinned signing-key fingerprint and + `libheif-freeworld` on Ikaros and Nymph. The package was confirmed installed on Ikaros on + 2026-10-03; Nymph deployment and an actual image-opening test were not observed. +- [ ] Validate Atlas iCloudPD filesystem/SELinux/SMB access, the next daily sync, ZFS/Borg/USB + backup inclusion, and isolated restore of photos and private state. A recursive hourly snapshot + of `zpool/archive` exists after ingestion, but no iCloudPD-specific backup version or restore + has been verified. The first monthly scrub remains a separate open data-protection check. ## Cerberus Management Node (Deferred) `cerberus` is postponed until the office in the new house is physically set up. It is not an inventory @@ -442,5 +469,5 @@ validated exports of older historical data will use a dedicated Atlas NFS datase `/etc/resolv.conf` linked to `/run/systemd/resolve/resolv.conf`. LAN clients may use AdGuard, but Aegis must use the independent upstream DNS declared by `aegis_host_dns_servers` so Greenboot does not depend on the AdGuard container during startup. -- iCloudPD requires post-deployment interactive MFA initialization; its cookie/configuration state is - persisted in `/var/lib/icloudpd/config`. +- Aegis iCloudPD has been retired and is no longer managed by this role. Its service, Quadlet, + data, and MFA state were removed with the operator's explicit authorization. diff --git a/README.it.md b/README.it.md index fc4fc70..1e25e98 100644 --- a/README.it.md +++ b/README.it.md @@ -527,12 +527,15 @@ della protezione dei dati: richiede storage applicativo, database e cache separa pubblicazione solo tramite NPM e Aegis, procedure di backup, aggiornamento e migrazione. Non distribuirlo prima di completare la checklist di protezione dei dati. -La destinazione futura per l'importazione foto iCloud è Atlas, non Aegis. Dopo la validazione dei -backup, pianificare una migrazione esplicita di iCloudPD con foto sotto `/zpool/archive/Pictures` e -stato applicativo/MFA fuori da `Archive`; testare permessi, SELinux, backup e restore prima del -cutover. L'attuale iCloudPD su Aegis e l'export NFS Photobook restano configurati fino -all'approvazione e alla verifica di questa migrazione separata. Anche il servizio Atlas sarà -temporaneo in attesa di Uranus. +Atlas è la destinazione dichiarata per iCloudPD. Ansible gestisce dataset, Quadlet rootless e +`icloudpd.conf` privato con Apple ID dal Vault: foto in `/zpool/archive/Pictures/iCloudPD`, +stato in `zpool/services/data/icloudpd`. Il primo avvio è stato manuale; password e MFA restano +gestiti interattivamente, senza avvio automatico al boot. L'inizializzazione è stata completata e +il download iniziale di foto e video è terminato il 2026-10-03. Su Aegis +il servizio, il Quadlet e `/var/lib/icloudpd` sono stati rimossi e verificati; il playbook Aegis +non contiene più task iCloudPD. L'accesso SMB e il ripristino dai backup dei nuovi dati restano +da verificare. L'export NFS Photobook resta +invariato. Dettagli in [`docs/atlas-icloudpd-migration.md`](docs/atlas-icloudpd-migration.md). Il primo ciclo pianificato del backup di Prometheus e una prova di disaster recovery a dimensione reale restano da verificare. Il 2026-09-30 una VM Rocky isolata ha superato ricostruzione OS con Ansible, diff --git a/README.md b/README.md index 56dcf22..0dba351 100644 --- a/README.md +++ b/README.md @@ -210,8 +210,8 @@ ansible/bootstrap/generate-aegis-ign.sh --write IMAGE DEVICE ``` The controller manages it remotely as `pi@aegis`; unlike local desktop profiles, Aegis is -intentionally an SSH inventory target. `profile_aegis` manages rootful Podman Quadlets for AdGuard -Home and iCloudPD, persistent data under `/var/lib`, the Podman auto-update timer, LAN-restricted +intentionally an SSH inventory target. `profile_aegis` manages a rootful Podman Quadlet for AdGuard +Home, its persistent data under `/var/lib`, the Podman auto-update timer, LAN-restricted firewalld rules, SSH key-only access for `pi`, the `nfs-utils` and `wireguard-tools` rpm-ostree layers, and `wake-ikaros`. `wireguard_overlay` makes Aegis the internal endpoint and LAN gateway for Prometheus: it enables persistent IPv4 forwarding, installs a scoped WireGuard-to-LAN firewalld policy, and source-NATs @@ -224,9 +224,8 @@ opened and closed manually during initial setup. The profile disables the local stub and points `/etc/resolv.conf` to its full resolver data, freeing port 53 for AdGuard. LAN clients may use AdGuard on Aegis, while Aegis itself uses the independent upstream DNS declared by `aegis_host_dns_servers`; this prevents Greenboot from depending on the AdGuard container during -startup. Reboot Aegis after changing its NetworkManager DNS profile. Define -`vault_aegis_icloudpd_apple_id` in Vault before applying it. iCloudPD still requires interactive MFA -initialization after its first deployment. +startup. Reboot Aegis after changing its NetworkManager DNS profile. iCloudPD was retired from Aegis; +the Aegis role no longer contains iCloudPD tasks. Atlas iCloudPD config is Vault-backed; MFA is manual. New Aegis images create the `admin` account in Butane. Before configuring a newly imaged node, run its first playbook execution with `-e ansible_user=admin`; the SSH hardening role then permits that same @@ -543,11 +542,14 @@ declared persistent application, database, and cache storage, Vault-backed crede publishing through Aegis, and defined backup, upgrade, and eventual migration procedures. Do not deploy it before the data-protection checklist is complete. -The desired future iCloud photo-ingestion host is Atlas, not Aegis. After data-protection validation, -plan an explicit iCloudPD migration with photos under `/zpool/archive/Pictures` and application/MFA -state outside `Archive`, then test permissions, SELinux, backups and recovery before cutting over. -The current Aegis iCloudPD service and Atlas Photobook NFS export remain configured until that -separate migration is approved and validated; the eventual Atlas service is temporary until Uranus. +Atlas is the declared iCloud photo-ingestion host. Ansible manages the rootless Quadlet, a private +Vault-backed `icloudpd.conf`, photos under `/zpool/archive/Pictures/iCloudPD`, and separate state in +`zpool/services/data/icloudpd`. The service was started manually; Ansible does not enable automatic +startup or manage the password and MFA keyring. The operator initialized MFA interactively; on +2026-10-03 the initial photo/video download completed. Aegis iCloudPD, including its service data, +has been removed and verified; the Aegis role no longer manages it. Backup/restore and SMB access +for the new data remain unverified. The Photobook NFS export remains untouched. See +[`docs/atlas-icloudpd-migration.md`](docs/atlas-icloudpd-migration.md). The first scheduled Prometheus backup runs and production-size disaster-recovery tests remain follow-up work. The prioritized operational backlog is kept in `AGENTS.md`. diff --git a/ansible/inventory/host_vars/aegis.yml b/ansible/inventory/host_vars/aegis.yml index b304a35..fba2e5d 100644 --- a/ansible/inventory/host_vars/aegis.yml +++ b/ansible/inventory/host_vars/aegis.yml @@ -42,5 +42,3 @@ aegis_ssh_authorized_keys: key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph" - name: siren key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren" - -aegis_icloudpd_apple_id: "{{ vault_aegis_icloudpd_apple_id | default('') }}" diff --git a/ansible/inventory/host_vars/atlas.yml b/ansible/inventory/host_vars/atlas.yml index 1a59d92..e5d63fe 100644 --- a/ansible/inventory/host_vars/atlas.yml +++ b/ansible/inventory/host_vars/atlas.yml @@ -140,8 +140,8 @@ atlas_monitor_remote_capacity: atlas_manage_sharing: true atlas_manage_media_stack: false # Planned after data-protection validation: move iCloudPD photo ingestion from -# Aegis to Atlas, with photos under /zpool/archive/Pictures and persistent -# application/MFA state outside Archive. Do not deploy or cut over yet. +# Aegis to Atlas, with photos under /zpool/archive/Pictures/iCloudPD and +# application/MFA state in a separate dataset. Do not deploy or cut over yet. # WireGuard is retired on Atlas. These rootless services are a temporary home # until Uranus replaces them. diff --git a/ansible/roles/packages_fedora/tasks/main.yml b/ansible/roles/packages_fedora/tasks/main.yml index 4cb32c8..c28571f 100644 --- a/ansible/roles/packages_fedora/tasks/main.yml +++ b/ansible/roles/packages_fedora/tasks/main.yml @@ -39,11 +39,41 @@ state: enabled when: "'workstation_dev_wsl' in group_names" +- name: Install distribution signing keys for Fedora desktop codecs + tags: [packages, heic] + ansible.builtin.dnf: + name: distribution-gpg-keys + state: present + when: "'graphical_desktop' in group_names" + +- name: Import RPM Fusion Free signing key for Fedora desktop codecs + tags: [packages, heic] + ansible.builtin.rpm_key: + key: /usr/share/distribution-gpg-keys/rpmfusion/RPM-GPG-KEY-rpmfusion-free-fedora-2020 + fingerprint: E9A491A3DE247814E7E067EAE06F8ECDD651FF2E + state: present + when: "'graphical_desktop' in group_names" + +- name: Enable RPM Fusion Free for Fedora desktop codecs + tags: [packages, heic] + ansible.builtin.dnf: + name: "https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-{{ ansible_facts['distribution_major_version'] }}.noarch.rpm" + state: present + when: "'graphical_desktop' in group_names" + - name: Refresh dnf package metadata tags: [packages] ansible.builtin.dnf: update_cache: true +- name: Install HEIC decoder on Fedora desktops + tags: [packages, heic] + ansible.builtin.dnf: + name: libheif-freeworld + state: present + update_cache: true + when: "'graphical_desktop' in group_names" + - name: Install packages on Fedora tags: [packages] ansible.builtin.dnf: diff --git a/ansible/roles/profile_aegis/defaults/main.yml b/ansible/roles/profile_aegis/defaults/main.yml index ef30210..c3d3f75 100644 --- a/ansible/roles/profile_aegis/defaults/main.yml +++ b/ansible/roles/profile_aegis/defaults/main.yml @@ -8,10 +8,6 @@ aegis_network_connection_uuid: "" aegis_host_dns_servers: [] aegis_host_dns_search_domains: [] aegis_adguard_image: docker.io/adguard/adguardhome:latest -aegis_icloudpd_image: docker.io/boredazfcuk/icloudpd:latest -aegis_icloudpd_folder_structure: '{:%Y/%m/%d}' -aegis_icloudpd_synchronisation_interval: 86400 -aegis_icloudpd_apple_id: "" aegis_ikaros_mac_address: aa:bb:cc:dd:ee:ff aegis_wol_port: 9 diff --git a/ansible/roles/profile_aegis/handlers/main.yml b/ansible/roles/profile_aegis/handlers/main.yml index 26fd306..3f6aa0e 100644 --- a/ansible/roles/profile_aegis/handlers/main.yml +++ b/ansible/roles/profile_aegis/handlers/main.yml @@ -9,13 +9,12 @@ name: sshd.service state: reloaded -- name: Restart Aegis Quadlet services +- name: Restart Aegis AdGuard Quadlet ansible.builtin.systemd: name: "{{ item }}" state: restarted daemon_reload: true loop: - adguardhome.service - - icloudpd.service loop_control: label: "{{ item }}" diff --git a/ansible/roles/profile_aegis/tasks/main.yml b/ansible/roles/profile_aegis/tasks/main.yml index 1452373..a3de813 100644 --- a/ansible/roles/profile_aegis/tasks/main.yml +++ b/ansible/roles/profile_aegis/tasks/main.yml @@ -13,14 +13,6 @@ msg: Reboot Aegis to activate the newly layered packages, then rerun the playbook. when: aegis_layered_packages_result.needs_reboot | default(false) -- name: Require Aegis iCloudPD Apple ID - tags: [aegis, icloudpd] - ansible.builtin.assert: - that: - - aegis_icloudpd_apple_id | length > 0 - fail_msg: Define vault_aegis_icloudpd_apple_id before applying the Aegis profile. - no_log: true - - name: Require completed Aegis network placeholders tags: [aegis, dns, firewall, network, services] ansible.builtin.assert: @@ -119,8 +111,6 @@ loop: - /var/lib/adguard/work - /var/lib/adguard/conf - - /var/lib/icloudpd/data - - /var/lib/icloudpd/config - name: Create Quadlet configuration directory tags: [aegis, containers] @@ -142,12 +132,9 @@ loop: - src: adguardhome.container.j2 dest: adguardhome.container - - src: icloudpd.container.j2 - dest: icloudpd.container loop_control: label: "{{ item.dest }}" - no_log: "{{ item.dest == 'icloudpd.container' }}" - notify: Restart Aegis Quadlet services + notify: Restart Aegis AdGuard Quadlet - name: Create Aegis systemd-resolved configuration directory tags: [aegis, adguard, dns, services] @@ -168,7 +155,7 @@ mode: "0644" notify: - Restart Aegis systemd-resolved - - Restart Aegis Quadlet services + - Restart Aegis AdGuard Quadlet - name: Point Aegis resolver at the full systemd-resolved configuration tags: [aegis, adguard, dns, services] @@ -361,7 +348,7 @@ group: root mode: "0755" -- name: Enable Aegis Quadlet services and automatic updates +- name: Enable Aegis AdGuard Quadlet and automatic updates tags: [aegis, containers, services] ansible.builtin.systemd: name: "{{ item }}" @@ -370,7 +357,6 @@ daemon_reload: true loop: - adguardhome.service - - icloudpd.service - podman-auto-update.timer loop_control: label: "{{ item }}" diff --git a/ansible/roles/profile_aegis/templates/icloudpd.container.j2 b/ansible/roles/profile_aegis/templates/icloudpd.container.j2 deleted file mode 100644 index e8f6aeb..0000000 --- a/ansible/roles/profile_aegis/templates/icloudpd.container.j2 +++ /dev/null @@ -1,20 +0,0 @@ -# Managed by Ansible. Do not edit manually. -[Unit] -Description=iCloud Photos Downloader -Wants=network-online.target -After=network-online.target - -[Container] -Image={{ aegis_icloudpd_image }} -Environment=apple_id={{ aegis_icloudpd_apple_id }} -Environment=folder_structure={{ aegis_icloudpd_folder_structure }} -Environment=synchronisation_interval={{ aegis_icloudpd_synchronisation_interval }} -Volume=/var/lib/icloudpd/data:/home/root/iCloud:Z -Volume=/var/lib/icloudpd/config:/config:Z -AutoUpdate=registry - -[Service] -Restart=always - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/profile_atlas/defaults/main.yml b/ansible/roles/profile_atlas/defaults/main.yml index a0e5975..19edfc9 100644 --- a/ansible/roles/profile_atlas/defaults/main.yml +++ b/ansible/roles/profile_atlas/defaults/main.yml @@ -194,6 +194,17 @@ atlas_gitea_restore_test: false atlas_gitea_final_restore: false atlas_gitea_restore_helper: /usr/local/libexec/atlas-gitea-restore-test +# Declare storage and an inactive Quadlet only. The operator supplies the +# private configuration, handles MFA, and starts the user service manually. +atlas_icloudpd_dataset: "{{ atlas_zfs_pool }}/services/data/icloudpd" +atlas_icloudpd_state_dir: "{{ atlas_app_data_mountpoint }}/icloudpd" +atlas_icloudpd_config_dir: "{{ atlas_icloudpd_state_dir }}/config" +atlas_icloudpd_photos_dir: "{{ atlas_archive_mountpoint }}/Pictures/iCloudPD" +atlas_icloudpd_image: >- + docker.io/boredazfcuk/icloudpd@sha256:9966c31ddf0b5b306ac2410b4edd5d626806d96e80c92b83cbb689972dc9389f +atlas_icloudpd_quadlet_dir: "{{ atlas_admin_home }}/.config/containers/systemd" +atlas_icloudpd_timezone: Europe/Rome + atlas_45drives_repo_url: https://repo.45drives.com/repofiles/rocky/45drives-enterprise.repo atlas_45drives_repo_file: /etc/yum.repos.d/45drives-enterprise.repo atlas_45drives_packages: diff --git a/ansible/roles/profile_atlas/handlers/main.yml b/ansible/roles/profile_atlas/handlers/main.yml index 8f84b6e..10d8757 100644 --- a/ansible/roles/profile_atlas/handlers/main.yml +++ b/ansible/roles/profile_atlas/handlers/main.yml @@ -1,4 +1,14 @@ --- +- name: Reload Atlas admin user manager + 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: not ansible_check_mode + - name: Reload SSH service ansible.builtin.systemd: name: sshd diff --git a/ansible/roles/profile_atlas/tasks/icloudpd.yml b/ansible/roles/profile_atlas/tasks/icloudpd.yml new file mode 100644 index 0000000..9695bd6 --- /dev/null +++ b/ansible/roles/profile_atlas/tasks/icloudpd.yml @@ -0,0 +1,165 @@ +--- +- name: Require exact Atlas iCloudPD paths and rootless identity + tags: [atlas, icloudpd] + ansible.builtin.assert: + that: + - atlas_manage_storage | bool + - atlas_icloudpd_dataset == atlas_zfs_pool ~ '/services/data/icloudpd' + - atlas_icloudpd_state_dir == atlas_app_data_mountpoint ~ '/icloudpd' + - atlas_icloudpd_config_dir == atlas_icloudpd_state_dir ~ '/config' + - atlas_icloudpd_photos_dir == atlas_archive_mountpoint ~ '/Pictures/iCloudPD' + - atlas_admin_uid | int == 1000 + - atlas_admin_gid | int == 1000 + - 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 + tags: [atlas, icloudpd] + block: + - name: Inspect the existing Archive and application-data datasets + community.general.zfs_facts: + name: "{{ item.dataset }}" + properties: name,mounted,mountpoint + loop: + - dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_archive }}" + mountpoint: "{{ atlas_archive_mountpoint }}" + - dataset: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_app_data }}" + mountpoint: "{{ atlas_app_data_mountpoint }}" + loop_control: + label: "{{ item.dataset }}" + register: atlas_icloudpd_parent_datasets + + - name: Refuse missing or unmounted iCloudPD parent datasets + ansible.builtin.assert: + that: + - item.ansible_facts.ansible_zfs_datasets | length == 1 + - item.ansible_facts.ansible_zfs_datasets[0].mounted == 'yes' + - item.ansible_facts.ansible_zfs_datasets[0].mountpoint == item.item.mountpoint + loop: "{{ atlas_icloudpd_parent_datasets.results }}" + loop_control: + label: "{{ item.item.dataset }}" + + - name: Inspect the existing Pictures namespace and proposed target + ansible.builtin.stat: + path: "{{ item }}" + follow: false + loop: + - "{{ atlas_archive_mountpoint }}/Pictures" + - "{{ atlas_icloudpd_photos_dir }}" + - "{{ atlas_icloudpd_photos_dir }}/.atlas-icloudpd-managed" + register: atlas_icloudpd_photo_paths + + - name: Refuse to adopt unrelated Pictures data or a symlink + ansible.builtin.assert: + that: + - atlas_icloudpd_photo_paths.results[0].stat.isdir | default(false) + - atlas_icloudpd_photo_paths.results[0].stat.uid | int == atlas_admin_uid | int + - >- + not atlas_icloudpd_photo_paths.results[1].stat.exists or + (atlas_icloudpd_photo_paths.results[1].stat.isdir | default(false) and + atlas_icloudpd_photo_paths.results[2].stat.isreg | default(false)) + fail_msg: >- + Pictures must exist and be admin-owned; an existing iCloudPD target + must carry its managed marker. Never adopt or replace unrelated data. + + - name: Create a dedicated ZFS dataset for iCloudPD configuration and MFA + community.general.zfs: + name: "{{ atlas_icloudpd_dataset }}" + state: present + extra_zfs_properties: + compression: zstd + mountpoint: "{{ atlas_icloudpd_state_dir }}" + + - name: Restrict iCloudPD state and the new photo subtree + ansible.builtin.file: + path: "{{ item.path }}" + state: directory + owner: "{{ atlas_admin_username }}" + group: "{{ atlas_admin_group }}" + mode: "{{ item.mode }}" + loop: + - path: "{{ atlas_icloudpd_state_dir }}" + mode: "0700" + - path: "{{ atlas_icloudpd_config_dir }}" + mode: "0700" + - path: "{{ atlas_icloudpd_photos_dir }}" + mode: "0750" + - path: "{{ atlas_icloudpd_quadlet_dir }}" + mode: "0700" + loop_control: + label: "{{ item.path }}" + + - name: Mark only the newly managed iCloudPD photo subtree + ansible.builtin.copy: + content: "Atlas iCloudPD photo subtree; do not remove source photos.\n" + dest: "{{ atlas_icloudpd_photos_dir }}/.atlas-icloudpd-managed" + owner: "{{ atlas_admin_username }}" + group: "{{ atlas_admin_group }}" + mode: "0600" + force: false + + - name: Install the image's required mounted-filesystem failsafe + ansible.builtin.copy: + content: "" + dest: "{{ atlas_icloudpd_photos_dir }}/.mounted" + owner: "{{ atlas_admin_username }}" + group: "{{ atlas_admin_group }}" + mode: "0644" + force: false + + - name: Require the Vault-backed iCloudPD Apple ID + ansible.builtin.assert: + that: + - vault_atlas_icloudpd_apple_id is defined + - vault_atlas_icloudpd_apple_id | length > 0 + - vault_atlas_icloudpd_apple_id != 'REPLACE_ME' + - vault_atlas_icloudpd_apple_id.splitlines() | length == 1 + fail_msg: Configure the existing iCloudPD Apple ID in Vault. + no_log: true + + - name: Seed private Atlas iCloudPD configuration when absent + ansible.builtin.template: + src: atlas-icloudpd.conf.j2 + dest: "{{ atlas_icloudpd_config_dir }}/icloudpd.conf" + owner: "{{ atlas_admin_username }}" + group: "{{ atlas_admin_group }}" + mode: "0600" + force: false + no_log: true + diff: false + + - name: Keep declared iCloudPD options in the image-managed configuration + ansible.builtin.lineinfile: + path: "{{ atlas_icloudpd_config_dir }}/icloudpd.conf" + regexp: "^{{ item.key }}=" + line: "{{ item.key }}={{ item.value }}" + owner: "{{ atlas_admin_username }}" + group: "{{ atlas_admin_group }}" + mode: "0600" + loop: + - {key: apple_id, value: "{{ vault_atlas_icloudpd_apple_id }}"} + - {key: authentication_type, value: MFA} + - {key: user, value: user} + - {key: user_id, value: "1000"} + - {key: group, value: group} + - {key: group_id, value: "1000"} + - {key: download_path, value: /home/user/iCloud} + - {key: folder_structure, value: "{:%Y/%m/%d}"} + - {key: directory_permissions, value: "750"} + - {key: file_permissions, value: "640"} + - {key: download_interval, value: "86400"} + - {key: auto_delete, value: "false"} + - {key: delete_after_download, value: "false"} + loop_control: + label: "{{ item.key }}" + no_log: true + diff: false + + - name: Render the rootless Atlas iCloudPD Quadlet + ansible.builtin.template: + src: atlas-icloudpd.container.j2 + dest: "{{ atlas_icloudpd_quadlet_dir }}/atlas-icloudpd.container" + owner: "{{ atlas_admin_username }}" + group: "{{ atlas_admin_group }}" + mode: "0644" + notify: Reload Atlas admin user manager diff --git a/ansible/roles/profile_atlas/tasks/main.yml b/ansible/roles/profile_atlas/tasks/main.yml index 7f09b9d..044b349 100644 --- a/ansible/roles/profile_atlas/tasks/main.yml +++ b/ansible/roles/profile_atlas/tasks/main.yml @@ -20,6 +20,9 @@ - name: Import staged Atlas rootless Gitea tasks ansible.builtin.import_tasks: gitea.yml +- name: Import Atlas iCloudPD storage and inactive Quadlet tasks + ansible.builtin.import_tasks: icloudpd.yml + - name: Import explicit Atlas Gitea restore rehearsal tasks ansible.builtin.import_tasks: gitea_restore.yml diff --git a/ansible/roles/profile_atlas/templates/atlas-icloudpd.conf.j2 b/ansible/roles/profile_atlas/templates/atlas-icloudpd.conf.j2 new file mode 100644 index 0000000..2d9504d --- /dev/null +++ b/ansible/roles/profile_atlas/templates/atlas-icloudpd.conf.j2 @@ -0,0 +1,14 @@ +# Managed by Ansible. Password, keyring and MFA cookies are stored separately in /config. +apple_id={{ vault_atlas_icloudpd_apple_id }} +authentication_type=MFA +user=user +user_id=1000 +group=group +group_id=1000 +download_path=/home/user/iCloud +folder_structure={:%Y/%m/%d} +directory_permissions=750 +file_permissions=640 +download_interval=86400 +auto_delete=false +delete_after_download=false diff --git a/ansible/roles/profile_atlas/templates/atlas-icloudpd.container.j2 b/ansible/roles/profile_atlas/templates/atlas-icloudpd.container.j2 new file mode 100644 index 0000000..635c9b2 --- /dev/null +++ b/ansible/roles/profile_atlas/templates/atlas-icloudpd.container.j2 @@ -0,0 +1,22 @@ +# Managed by Ansible. No install target or automatic start. +[Unit] +Description=Atlas rootless iCloud Photos Downloader +RequiresMountsFor={{ atlas_icloudpd_state_dir }} {{ atlas_icloudpd_photos_dir }} + +[Container] +ContainerName=atlas-icloudpd +Image={{ atlas_icloudpd_image }} +UserNS=keep-id:uid=1000,gid=1000 +# The image initialises its unprivileged UID 1000 account as container root. +User=0 +# Upstream launcher requires traceroute for its iCloud reachability check. +AddCapability=NET_RAW +Environment=TZ={{ atlas_icloudpd_timezone }} +Volume={{ atlas_icloudpd_photos_dir }}:/home/user/iCloud:z +Volume={{ atlas_icloudpd_config_dir }}:/config:Z +NoNewPrivileges=true + +[Service] +Restart=on-failure +RestartSec=300 +TimeoutStartSec=900 diff --git a/docs/atlas-icloudpd-migration.md b/docs/atlas-icloudpd-migration.md new file mode 100644 index 0000000..02b2d94 --- /dev/null +++ b/docs/atlas-icloudpd-migration.md @@ -0,0 +1,163 @@ +# iCloudPD: Aegis to Atlas + +Atlas is the temporary ingestion host until Uranus. Aegis iCloudPD and its +state were retired. Ansible declares Atlas storage, the rootless Quadlet, +and a private `icloudpd.conf` with the Apple ID from the existing Vault key. +The password, keyring and MFA cookies remain application-managed; initialization +is interactive. +Do not place cookies, keyring files, passwords, or the Apple ID in this document, +unencrypted repository content, or a terminal transcript. + +## Historical source and current destination (2026-10-02) + +- Before retirement, Aegis' rootful `icloudpd.service` was active (no reported restarts, running + since 2026-07-25), but its declared data bind `/var/lib/icloudpd/data` + has **zero top-level entries** and is 4 KiB as observed on 2026-10-02. + Its persistent config has two top-level entries. `pi` cannot run passwordless + sudo, so the container's internal filesystem and root-only state have **not** + been audited. Do not conclude there are no photos to preserve: they could be + inside the container overlay because the declared bind targets the wrong + home. The current + Quadlet mounts that data directory at `/home/root/iCloud`; the image's + documented default is `/home/user/iCloud` with its default `user=user`. +- The non-secret `folder_structure` value in the persisted Aegis config is a + systemd generator path, **not** `{:%Y/%m/%d}`. The Quadlet passes percent + characters in `Environment=` without systemd escaping; that is the likely + cause. A running unit therefore does not prove that Aegis ingests photos. + Do not copy this config or assume that its MFA state is usable on Atlas. +- Atlas' `zpool` is healthy. `/zpool/archive/Pictures` already contains about + 25 GiB of unrelated data; iCloudPD gets only a new managed + `/zpool/archive/Pictures/iCloudPD` subtree. Both that subtree and + `zpool/services/data/icloudpd` were created on 2026-10-02. Never rsync with `--delete` into + Pictures or adopt its existing contents. `/zpool/media/photobook` is reserved + for Immich and remains untouched, including its Aegis-only NFS export. + +The upstream image documents `/config/icloudpd.conf` as its primary +configuration (environment configuration is deprecated), an exact +`/home/${user}/iCloud/.mounted` failsafe, and an interactive `--Initialise` +step for keyring and MFA cookies. The configuration must use the same download +path, user/UID, and folder format as the bind mounts. References: +[image configuration](https://github.com/boredazfcuk/docker-icloudpd/blob/master/CONFIGURATION.md), +[Podman user namespaces](https://docs.podman.io/en/latest/markdown/podman-pod.unit.5.html). + +## Declared Atlas target + +| Item | Location or policy | +| --- | --- | +| Downloaded photos | `/zpool/archive/Pictures/iCloudPD`, a new managed subtree of the SMB `Archive` dataset | +| Config, keyring, MFA cookies | `zpool/services/data/icloudpd` at `/zpool/services/data/icloudpd/config`, outside Archive | +| Host service owner | `admin` rootless user manager; no rootful Quadlet or published port | +| Container identity | Entry process root in its user namespace; downloader UID/GID 1000 maps to host `admin` | +| Image | Digest-pinned `docker.io/boredazfcuk/icloudpd`, with no registry auto-update | +| SELinux | Private `:Z` config bind; shared `:z` photo bind because Archive is also exposed through SMB and used by Syncthing. The label and SMB behavior require runtime testing. | +| Access | The new subtree is `admin:admin` mode 0750. No Photobook ownership, ACL, or export changes. | +| Sync policy | Daily interval; explicit directory/file modes 750/640; no iCloud deletion and no deletion of destination-only files | + +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 +`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. + +The previous gated check-mode tests and isolated Quadlet-generator test proved +only the proposed layout; they predate the simplified declarative role. They +were not a production deployment or an authentication test. + +## Evidence already gathered without production writes + +The digest-pinned image was pulled into **admin's** Atlas Podman store. An +isolated `/var/tmp` test ran with no network, a fake Apple ID, private temporary +config/photo mounts, `keep-id:uid=1000,gid=1000`, and no new privileges. Both +container root and UID 1000 wrote to the mounts; UID +1000's files mapped to host `admin`. A short-lived container remained running, +retained the intended `/home/user/iCloud` and literal `{:%Y/%m/%d}` config, +and saw an admin-owned `.mounted` marker. The container and temporary files +were removed. A second isolated test showed that dropping **all** container +capabilities prevents its root entrypoint from reading an admin-owned 0600 +config; with the default rootless user-namespace capabilities it could read +and write that file. The Quadlet retains `NoNewPrivileges=true` but does not +drop every capability. This proves only the container layout and namespace mapping, +**not** Apple authentication, a real download, SMB visibility, scheduled +operation, backup coverage, or recovery. + +The earlier disposable Photobook ACL test is superseded by the operator's +clarification that Photobook belongs to Immich. It is not evidence for the +current Archive destination, and the proposed Photobook ACL change was never +deployed. + +Backup path review on 2026-10-02: the managed Borg and USB scripts snapshot +the pool recursively and bind every mounted child dataset, so both +`archive` and the proposed `services/data/icloudpd` fall within their +declared source scope. Borg's runner switches to the dedicated `borg` account +with only `CAP_DAC_READ_SEARCH`; a read-only check using those exact `setpriv` +capability flags could traverse/read Archive, whereas plain +`sudo -u borg` could not. USB copies as root and preserves POSIX ACLs, but not +generic xattrs/SELinux labels. **This was scope and permission evidence, not a +completed backup or restore of iCloudPD data**, which did not exist at the time. + +## Validation status and remaining checks + +- Aegis retirement is complete: `icloudpd.service` is `not-found`/`inactive`, + the rootful Quadlet and `/var/lib/icloudpd` are absent, and AdGuard is active. + The temporary retirement tasks are no longer in the Aegis role. The Podman + image cache may remain; it is not service data. +- Atlas storage and the `admin` Quadlet are deployed. The second Ansible + run changed nothing and did not start the service; a later manual start + generated the config. `/zpool/media/photobook` was unchanged. +- The image generated `/zpool/services/data/icloudpd/config/icloudpd.conf` + 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. +- 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 + private state. A recursive hourly `zpool/archive` snapshot exists after + ingestion, but no iCloudPD-specific backup restore has passed. The first + real scrub and measured recovery targets are separate open items. + +On 2026-10-02 Atlas storage and the inactive Quadlet were deployed; a second +Ansible run made zero changes. The generated service was inactive, and no +`icloudpd.conf` existed. Two interactive-sudo Aegis runs removed its service, +Quadlet and `/var/lib/icloudpd`, then cleared the failed-unit record left by a +SIGKILL during shutdown. Read-only verification found `LoadState=not-found`, +`ActiveState=inactive`, both paths absent, and AdGuard active. + +On 2026-10-02 the operator requested the first manual start. The rootless +service stayed active, and the image generated `icloudpd.conf` under the +private config dataset. Its mode was tightened from 0644 to 0600. The generated +`apple_id` field is empty; no MFA or download is verified. The service has no +boot-time install target, so it is not configured for automatic startup. + +The 2026-10-02 Atlas `icloudpd` run rendered the Vault-backed template without +printing its contents; the second run made zero changes. File owner is +`admin:admin`, mode 0600, and the Apple ID field is nonempty. The rootless +service remained active with zero restarts. At that point keyring initialization, +cookie creation and a real download were unverified. The template now reads +`vault_atlas_icloudpd_apple_id`, which is already present in the encrypted +Vault; no password or MFA code was added to the template. + +The attempted interactive initialization then lost its container. Diagnosis +found that the image launcher requires `traceroute` to pass its iCloud +reachability check. Rootless Podman without `NET_RAW` returned `Operation not +permitted` despite working Atlas/container DNS and host HTTPS. An isolated +container with only `CAP_NET_RAW` passed the same check. The Quadlet now grants +that single capability while keeping `NoNewPrivileges=true`; a manual restart +passed `traceroute`, and the app stayed running. Logs then showed only the missing +keyring and a wait for `--Initialise` again. The app expanded the generated config +on startup, so Ansible now seeds it only when absent and idempotently maintains +only its declared options. A second live Ansible run made zero changes. At +that point MFA, actual ingestion, and backup/restore were unverified. + +On 2026-10-03, after interactive initialization, the rootless service was +active and the previous 24h of logs showed download activity with no +authentication failures or errors. At 02:16 the application reported `All +photos and videos have been downloaded` and `Download complete for user`. +The destination contained 11,658 files totaling 86,020,430,015 bytes; this +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. diff --git a/secrets/vault.yml b/secrets/vault.yml index 6467a45..b829d93 100644 --- a/secrets/vault.yml +++ b/secrets/vault.yml @@ -1,83 +1,71 @@ $ANSIBLE_VAULT;1.1;AES256 -61353065386233646137323235306631353635663530363237636231316265643562353465323430 -6165646466623962313835313537633137633766373930380a316335323962616265643136346666 -63336133336131346336383534356637623831363138323165633262386333363535393365383233 -6234393835653439370a313963313365373633323464343263383661383336363662633133643232 -34366634383862363635653034313531623330396639616462343630326162316535643465653532 -36326534333637376462353561343964633636366331363833313263353133383636623537303663 -35393032316439336666343161653439643638376134363535656262343963393365623432336433 -35383934313762313037326430316666363731666231336534326661353034333063643364343230 -65333739303566366263333565333465613136646237623937393733623438613832393634663463 -39376131313234333039633735613233373931613232653036663665316636303961653834366339 -36353730316132316233303964303839363161346564396163336137663134353062363733656430 -37643339326661653031376265646132623162373562393437373437313732396537383939333666 -62353036316633306666313461663033303830393765396131643035353730383931646239663935 -32626461316364386135303761383837613063336466363162323332663764616464373565383231 -61346463336566346533326535376439643133613762383633396131323632356533636139336365 -62393838316634623932643034376631333539343965383436613364643962363834346337353334 -32656439366439313734353963343133333533653839613632323338336131373566613835393536 -31663433616334373432376531346435336530303936356461303163646463613661643161313661 -66663866343565616631616338353737356164353562366164383736346131666662623132333466 -39383865653631373232393433663430643961646265386166333137643966303834363262373636 -62396434373363353636376133666133663162653265313139313732353639336232333862643036 -64386231336561396537326139346566306434633934343038663165396665363032383466633662 -62336163633964363435386630343966333162333730336138333239646631633132663931376462 -33663139356261313065376636613930353735396131306538306664646135636336643032623131 -38346264333331353633326535326431626563323036313665643337353563333339646430386564 -31613435383036313430316366323636663735326336393338353835323861333564363832656462 -35336435623261326363633033316130393062616339353263643062633331646137376135656365 -35636139336564346164616235616431326531333433646330386134323932373339646536356464 -66343533326534326165323564663533653666633035343163633832393361336462343937623165 -62383931326630363036396333313931393836366439653433623165666166356338653364336534 -35333936653833386163633738326164386166613561333530633937343230363366333662666539 -39333361633933663735303438663239303536363433313962643137386533633539326365383765 -37636538386339333935386132353265353031643662616330316463623661663738353433313830 -36373963633166333464653338343830373063323536383364393033393235326639613662343737 -38663362636331343061646465313237313431373433353361353265333766633463353632646536 -31323231306138323031396630656538363930373439336234343963616334363632653738316465 -63653938373830336362313238656266613362636634616537653863336132343931616262396130 -66393239303866656232653832343132366537333537343635666563343639323433383163613335 -39613533376634316133633430303535306266656333626264343733666335393661666561396633 -39346265316137326465326635396362333565393133623637633132616232326263663662343137 -33363733306135363361643031306265363733656362386666306334333035393839636533343363 -35396638616636633639343930373136376339346162393061393765363837646365383866636131 -33653465666239393133616232636231333332396138376332393664343364643835306530393238 -34663237303530303837663535646263393931373531393039356336316561653130356262636562 -38336362326639653237626634376334666565653036353236313634376364626338646538386536 -38626636386466373566646166393963643164343536373236396138303532393161363335386638 -32633032393737626363613463323366366637616361313537356136626661626633613739323338 -35383963666431343566356562333234663936376562616638636261303466633539376334303331 -39303834663234663063356233313962326664383839393832303462643636393034383434303465 -64333635376135326333356435373734643430623736373234643335343130383066326436356664 -63346663326364343634303930343338336139313864316165366232643537366635653764353763 -31363863633261643263303433373330366161323166366462336332313135366338393334653764 -66353733653137663835663731373364613030373334663061313433373861613665363236633130 -65613965366636343465336533613438373466383737373366653965633437323562643966396431 -39303033643438633762633263326132663466643438656366363431616237633031333936313831 -30323930383233313032323638356333626230333764363662313662646536643839353032353462 -30326166653937353130623133303533343934633565393831623033303234316330353432313266 -30636536633933376365623665616262663236383731633633346232613366333137396139306363 -35633336643266326335303261666666653536666630613639376336373237646134306462616537 -33343561373162666332613634643837343566646161373065366637653135613632353334636363 -63363232303963646530333366663862323264326536643337323266396566316233613630303637 -66646366376466373931613734363931316230323063373666653062373364396433633762633762 -38613933323733653238383935623230383562646563363833653838636165626365646537383639 -33666535656363393562316336633439636138373365623431393965653765306138646234663938 -65653133663663393731646337386535333261643932336132396237323930306136643534353930 -65636438396432623034626561613137336138623265393064383034623863303166356138393564 -37373164626634653662326234333539663735323464613334616130643937373730363263633366 -31393937326432386165343338313031376565313866363731643534313233303064373935303538 -31343832336230393636653432653162336361383963633766343461653466316337353931333363 -63313137303564336630343937356564643763383764613362366634373362666465626334336539 -64366533376165306532343461613265366266383862323032333465336161663161376630316465 -30306562666163646235656664653635366461366435663961623635383437663564356563346462 -31636234663765623838333237393239373564366262613637363938653463396530613963643837 -38636634376637366332623035313465393762653865623130336263343663303066366135616639 -63333964356466613038303263366462346261353030646532366361393965306435613131316463 -65366266376637323764643239323730366565633335666638666334663635373961303637383861 -35313431646434656562333937663837393038386361616630626532636339306432353434656165 -33663261383166386432383465666136376237346565303164363461666663346130346162316338 -62373061353034316234303835663439396434343738303764376665336239626238386436386234 -61306166383637366266393730323732386163366261393630336431633862353761343763363665 -61323039396234393835303633363339373633653334343766653032313230343464326664356566 -3462623830666664626633373966363866333337383730313066 +37646664613266633436346262613633613830623366383138613432366365373765353230333134 +3332333764313337396637323133623937343738373133370a333930356365653034323235643230 +36633864343161653833356636373931383761663864663334336236373733326266386639366335 +3131313661313637320a313937633361646333333962303335333233346166343831373039663964 +33366532386135663463643965363766643063616436316463666232666138323236346231303537 +34303535333866376430363063623934623761373865656231656661383935393866353566346430 +64333434613432376436343438343561383235366631623730653533633535326237666265653439 +34366264653665643063663361313339663034323932326233366636326336323432303434373765 +36316532316265343434383438623239666232373633626330333464303361643630303635643834 +39313136623830303762313462343637633763626333393033346637663931663238653734626131 +38393963646563333732353531653239643330326539643538323164343934356166343034316565 +33346431333735636537613930383331393265313962626234363237373562313231393061326439 +64363765323935316661353531366165343139633963336139313737306332613364643031666161 +30386362643930316265616564306336633133303166363665333462316265313364393939306162 +31303639313933356337386134623934663461643161306666633261653538633232343036653833 +66316466636233343136393765636333353230353738313833333265663238303730313936326664 +38373239353162363438323964333030666563346161643437326335666162356264396135393532 +63363862373136346532653734336335616132386237303031363433663132343861633937386130 +30633938616364303462303030303966303939633066393264303462393730363233373937356439 +36663533376232663737613734653532313136343939663539373866333638396266666163383864 +63613532393334373539346338616163383637633237666234613437663966653733616361353830 +61656666376133363330633863346637376266343134633037313132313361366638616261363839 +39393062396237666333303937363536346561343763663133323236393037383532396465336138 +35613463356532376534386433626337613030343266353332306462306463336336343830666138 +33656138363837633337393865643633623261613335366263643162663637623636666162653632 +30626238616266323332616234393838343330663662393433366630393566316336636530303165 +38343665623437356636643236393734396264356632326133623264633862633333626330336663 +61376263656665653731636133316161653635323138303866623862303065366232633736623336 +39656666386435343062656138313061616661313966326432663236626631316162623961616636 +35343939613262303066626537396164616666316265643065373638663436643961336138313862 +39666163646538356338356631346534633139643636393866646462646533363265663234633761 +65363661336138353239656165393836386134666331663036653132306433343764643666306333 +33623661626565633333306337303263633335386632386330353730316436313931326164363862 +37616265653161633632353865346639653961653836353962303762336535666266386535363165 +39653138646663376634323131613463333035326639313266613830616431316131383464353533 +62656634346637636164626461613137303461633761336232373133653532323566303136663030 +30633337346534636566343934306662356238396365306563336666623435353731613136333036 +36343436373932323265306639363761353364383635333136366231373166613861633032343233 +61376338616630343639333964356162613332323835333730333135356665383431626138643534 +66393966666465303763316230386538393863303063386564303165303962346139373338303436 +39373032663538323532323766353864643338326561313564373562616430326264386362666532 +36613132306462336631363035343732636465343562643430343035373961366566383130656165 +64613938393265343037633161653937323933646637653036306532366237313838346361333932 +34663565653264626137323239336532643262356166633665313761336162303635346666383863 +61383930383033626337383366353766393536653135383062656639323361353539356232613736 +63646235663363333333623463313961326533653236363938383765663439613832653039386436 +38393734633536313731323437336332353564363564333736663037386530333639326338656561 +66336637353238383231613666313261383234336531666132396230373931623363323832633064 +39613036346166393936613939363865616135653830366435643538336365353333613831353962 +36623537363434373137633063373934383439333462646361613737303239643834303535366138 +34656465316431656461373737643537303936636539383934373831616438343965373765373535 +63653164363731653030303466646539636361383664343763646163663238383435653035653666 +63383165626365653261303834333234626534396333353231303261396361616233363334383336 +33316462636133336132656364613439396131613565646565396365316238323962353462653736 +64386139313266663963643962363133386133393166306163626632646463333363323830306164 +35353936653137383761326132373739306163613764386531613032313235373331303530383633 +64646533313434653734366233633535323564386431306538633666383661303038613330653832 +34643463396137643034353439653334653836333161396130363637326339383363303037306330 +61353635633334343432646461396439393439383639336139316161373737333961653731393333 +61636164343838346365373736356161386430356533303331333838333732363233613931613863 +66633662383466306332366563373865323861323833353238356563363635313463366333653432 +65303839653963376566383737346231343663363363313332383365646363373737323839613564 +34613362303335316363363661653639386538326337386537333765643161613961316531613563 +38386564636637643762643830666138383361396233303339643665343261356462393830376662 +32656334346536636536343263336565333234353831616565366538393661353561376538346334 +61396135623230366433303932396130636331333263316333643861626564343330386636613063 +32383061616435643736653264313839363232346332343565336464353138396339623533393237 +38353632646565323735643462626239663736643033643231613464663866663262366632353434 +37363866343239363131633464316133396462353336613962306332343563333962333934616330 +3536356634376131633039373834376533633065303533653333 diff --git a/secrets/vault.yml.example b/secrets/vault.yml.example index ec0ba75..3e3e0b6 100644 --- a/secrets/vault.yml.example +++ b/secrets/vault.yml.example @@ -8,7 +8,7 @@ vault_git_work_email: "REPLACE_ME" vault_git_work_gpg: "REPLACE_ME" vault_ikaros_authorized_ssh_keys: - "ssh-ed25519 REPLACE_ME" -vault_aegis_icloudpd_apple_id: "REPLACE_ME" +vault_atlas_icloudpd_apple_id: "REPLACE_ME" vault_atlas_admin_password_hash: "REPLACE_WITH_A_SHADOW_COMPATIBLE_HASH" vault_atlas_samba_password: "REPLACE_ME" vault_atlas_immich_db_password: "REPLACE_ME"