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
This commit is contained in:
Fabio Scotto di Santolo
2026-10-03 09:59:59 +02:00
committed by GitHub
parent 9e76309833
commit 4bd6aafb53
19 changed files with 550 additions and 153 deletions

View File

@@ -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` `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):
`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`
- Atlas explicit isolated Gitea restore rehearsal (not part of normal runs): - 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. container paths, and the required Vault database secret.
### Priority 4 - Optional workflows ### Priority 4 - Optional workflows
- [ ] After data protection is validated, move iCloudPD photo ingestion from Aegis to Atlas as a - [x] Deploy the declared Atlas iCloudPD state dataset and inactive rootless `admin` Quadlet.
temporary service until Uranus is ready. Plan to store photos in `/zpool/archive/Pictures` and Photos belong under `/zpool/archive/Pictures/iCloudPD`; private config/MFA state belongs in
persistent application/MFA state outside `Archive`; validate permissions, SELinux, backups and `zpool/services/data/icloudpd`. Photobook remains reserved for Immich. Ansible now renders
recovery before cutover. Keep the current Aegis service and Photobook NFS export unchanged until `icloudpd.conf` with the Apple ID from the existing Vault key, but does not store the password,
the Atlas workflow is tested, then retire them explicitly if no longer needed. 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 Management Node (Deferred)
`cerberus` is postponed until the office in the new house is physically set up. It is not an inventory `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 `/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 Aegis must use the independent upstream DNS declared by `aegis_host_dns_servers` so Greenboot does
not depend on the AdGuard container during startup. not depend on the AdGuard container during startup.
- iCloudPD requires post-deployment interactive MFA initialization; its cookie/configuration state is - Aegis iCloudPD has been retired and is no longer managed by this role. Its service, Quadlet,
persisted in `/var/lib/icloudpd/config`. data, and MFA state were removed with the operator's explicit authorization.

View File

@@ -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 pubblicazione solo tramite NPM e Aegis, procedure di backup, aggiornamento e migrazione. Non
distribuirlo prima di completare la checklist di protezione dei dati. 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 Atlas è la destinazione dichiarata per iCloudPD. Ansible gestisce dataset, Quadlet rootless e
backup, pianificare una migrazione esplicita di iCloudPD con foto sotto `/zpool/archive/Pictures` e `icloudpd.conf` privato con Apple ID dal Vault: foto in `/zpool/archive/Pictures/iCloudPD`,
stato applicativo/MFA fuori da `Archive`; testare permessi, SELinux, backup e restore prima del stato in `zpool/services/data/icloudpd`. Il primo avvio è stato manuale; password e MFA restano
cutover. L'attuale iCloudPD su Aegis e l'export NFS Photobook restano configurati fino gestiti interattivamente, senza avvio automatico al boot. L'inizializzazione è stata completata e
all'approvazione e alla verifica di questa migrazione separata. Anche il servizio Atlas sarà il download iniziale di foto e video è terminato il 2026-10-03. Su Aegis
temporaneo in attesa di Uranus. 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 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, restano da verificare. Il 2026-09-30 una VM Rocky isolata ha superato ricostruzione OS con Ansible,

View File

@@ -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 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 intentionally an SSH inventory target. `profile_aegis` manages a rootful Podman Quadlet for AdGuard
Home and iCloudPD, persistent data under `/var/lib`, the Podman auto-update timer, LAN-restricted 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, 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: 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 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 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 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 `aegis_host_dns_servers`; this prevents Greenboot from depending on the AdGuard container during
startup. Reboot Aegis after changing its NetworkManager DNS profile. Define startup. Reboot Aegis after changing its NetworkManager DNS profile. iCloudPD was retired from Aegis;
`vault_aegis_icloudpd_apple_id` in Vault before applying it. iCloudPD still requires interactive MFA the Aegis role no longer contains iCloudPD tasks. Atlas iCloudPD config is Vault-backed; MFA is manual.
initialization after its first deployment.
New Aegis images create the `admin` account in Butane. Before configuring a newly imaged node, run its 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 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 publishing through Aegis, and defined backup, upgrade, and eventual migration procedures. Do not deploy
it before the data-protection checklist is complete. it before the data-protection checklist is complete.
The desired future iCloud photo-ingestion host is Atlas, not Aegis. After data-protection validation, Atlas is the declared iCloud photo-ingestion host. Ansible manages the rootless Quadlet, a private
plan an explicit iCloudPD migration with photos under `/zpool/archive/Pictures` and application/MFA Vault-backed `icloudpd.conf`, photos under `/zpool/archive/Pictures/iCloudPD`, and separate state in
state outside `Archive`, then test permissions, SELinux, backups and recovery before cutting over. `zpool/services/data/icloudpd`. The service was started manually; Ansible does not enable automatic
The current Aegis iCloudPD service and Atlas Photobook NFS export remain configured until that startup or manage the password and MFA keyring. The operator initialized MFA interactively; on
separate migration is approved and validated; the eventual Atlas service is temporary until Uranus. 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 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`. operational backlog is kept in `AGENTS.md`.

View File

@@ -42,5 +42,3 @@ aegis_ssh_authorized_keys:
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph" key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEH/7GJfGt0ZVmKeEzceoFkFkeCXFryKK9vAbaip+HCx nymph"
- name: siren - name: siren
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren" key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA95wYlzpfN3rjUhpMeP4KHn8I6ZrjQXoDTgwgRIa++b siren"
aegis_icloudpd_apple_id: "{{ vault_aegis_icloudpd_apple_id | default('') }}"

View File

@@ -140,8 +140,8 @@ atlas_monitor_remote_capacity:
atlas_manage_sharing: true atlas_manage_sharing: true
atlas_manage_media_stack: false atlas_manage_media_stack: false
# Planned after data-protection validation: move iCloudPD photo ingestion from # Planned after data-protection validation: move iCloudPD photo ingestion from
# Aegis to Atlas, with photos under /zpool/archive/Pictures and persistent # Aegis to Atlas, with photos under /zpool/archive/Pictures/iCloudPD and
# application/MFA state outside Archive. Do not deploy or cut over yet. # 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 # WireGuard is retired on Atlas. These rootless services are a temporary home
# until Uranus replaces them. # until Uranus replaces them.

View File

@@ -39,11 +39,41 @@
state: enabled state: enabled
when: "'workstation_dev_wsl' in group_names" 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 - name: Refresh dnf package metadata
tags: [packages] tags: [packages]
ansible.builtin.dnf: ansible.builtin.dnf:
update_cache: true 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 - name: Install packages on Fedora
tags: [packages] tags: [packages]
ansible.builtin.dnf: ansible.builtin.dnf:

View File

@@ -8,10 +8,6 @@ aegis_network_connection_uuid: ""
aegis_host_dns_servers: [] aegis_host_dns_servers: []
aegis_host_dns_search_domains: [] aegis_host_dns_search_domains: []
aegis_adguard_image: docker.io/adguard/adguardhome:latest 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_ikaros_mac_address: aa:bb:cc:dd:ee:ff
aegis_wol_port: 9 aegis_wol_port: 9

View File

@@ -9,13 +9,12 @@
name: sshd.service name: sshd.service
state: reloaded state: reloaded
- name: Restart Aegis Quadlet services - name: Restart Aegis AdGuard Quadlet
ansible.builtin.systemd: ansible.builtin.systemd:
name: "{{ item }}" name: "{{ item }}"
state: restarted state: restarted
daemon_reload: true daemon_reload: true
loop: loop:
- adguardhome.service - adguardhome.service
- icloudpd.service
loop_control: loop_control:
label: "{{ item }}" label: "{{ item }}"

View File

@@ -13,14 +13,6 @@
msg: Reboot Aegis to activate the newly layered packages, then rerun the playbook. msg: Reboot Aegis to activate the newly layered packages, then rerun the playbook.
when: aegis_layered_packages_result.needs_reboot | default(false) 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 - name: Require completed Aegis network placeholders
tags: [aegis, dns, firewall, network, services] tags: [aegis, dns, firewall, network, services]
ansible.builtin.assert: ansible.builtin.assert:
@@ -119,8 +111,6 @@
loop: loop:
- /var/lib/adguard/work - /var/lib/adguard/work
- /var/lib/adguard/conf - /var/lib/adguard/conf
- /var/lib/icloudpd/data
- /var/lib/icloudpd/config
- name: Create Quadlet configuration directory - name: Create Quadlet configuration directory
tags: [aegis, containers] tags: [aegis, containers]
@@ -142,12 +132,9 @@
loop: loop:
- src: adguardhome.container.j2 - src: adguardhome.container.j2
dest: adguardhome.container dest: adguardhome.container
- src: icloudpd.container.j2
dest: icloudpd.container
loop_control: loop_control:
label: "{{ item.dest }}" label: "{{ item.dest }}"
no_log: "{{ item.dest == 'icloudpd.container' }}" notify: Restart Aegis AdGuard Quadlet
notify: Restart Aegis Quadlet services
- name: Create Aegis systemd-resolved configuration directory - name: Create Aegis systemd-resolved configuration directory
tags: [aegis, adguard, dns, services] tags: [aegis, adguard, dns, services]
@@ -168,7 +155,7 @@
mode: "0644" mode: "0644"
notify: notify:
- Restart Aegis systemd-resolved - Restart Aegis systemd-resolved
- Restart Aegis Quadlet services - Restart Aegis AdGuard Quadlet
- name: Point Aegis resolver at the full systemd-resolved configuration - name: Point Aegis resolver at the full systemd-resolved configuration
tags: [aegis, adguard, dns, services] tags: [aegis, adguard, dns, services]
@@ -361,7 +348,7 @@
group: root group: root
mode: "0755" mode: "0755"
- name: Enable Aegis Quadlet services and automatic updates - name: Enable Aegis AdGuard Quadlet and automatic updates
tags: [aegis, containers, services] tags: [aegis, containers, services]
ansible.builtin.systemd: ansible.builtin.systemd:
name: "{{ item }}" name: "{{ item }}"
@@ -370,7 +357,6 @@
daemon_reload: true daemon_reload: true
loop: loop:
- adguardhome.service - adguardhome.service
- icloudpd.service
- podman-auto-update.timer - podman-auto-update.timer
loop_control: loop_control:
label: "{{ item }}" label: "{{ item }}"

View File

@@ -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

View File

@@ -194,6 +194,17 @@ atlas_gitea_restore_test: false
atlas_gitea_final_restore: false atlas_gitea_final_restore: false
atlas_gitea_restore_helper: /usr/local/libexec/atlas-gitea-restore-test 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_url: https://repo.45drives.com/repofiles/rocky/45drives-enterprise.repo
atlas_45drives_repo_file: /etc/yum.repos.d/45drives-enterprise.repo atlas_45drives_repo_file: /etc/yum.repos.d/45drives-enterprise.repo
atlas_45drives_packages: atlas_45drives_packages:

View File

@@ -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 - name: Reload SSH service
ansible.builtin.systemd: ansible.builtin.systemd:
name: sshd name: sshd

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -1,83 +1,71 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
61353065386233646137323235306631353635663530363237636231316265643562353465323430 37646664613266633436346262613633613830623366383138613432366365373765353230333134
6165646466623962313835313537633137633766373930380a316335323962616265643136346666 3332333764313337396637323133623937343738373133370a333930356365653034323235643230
63336133336131346336383534356637623831363138323165633262386333363535393365383233 36633864343161653833356636373931383761663864663334336236373733326266386639366335
6234393835653439370a313963313365373633323464343263383661383336363662633133643232 3131313661313637320a313937633361646333333962303335333233346166343831373039663964
34366634383862363635653034313531623330396639616462343630326162316535643465653532 33366532386135663463643965363766643063616436316463666232666138323236346231303537
36326534333637376462353561343964633636366331363833313263353133383636623537303663 34303535333866376430363063623934623761373865656231656661383935393866353566346430
35393032316439336666343161653439643638376134363535656262343963393365623432336433 64333434613432376436343438343561383235366631623730653533633535326237666265653439
35383934313762313037326430316666363731666231336534326661353034333063643364343230 34366264653665643063663361313339663034323932326233366636326336323432303434373765
65333739303566366263333565333465613136646237623937393733623438613832393634663463 36316532316265343434383438623239666232373633626330333464303361643630303635643834
39376131313234333039633735613233373931613232653036663665316636303961653834366339 39313136623830303762313462343637633763626333393033346637663931663238653734626131
36353730316132316233303964303839363161346564396163336137663134353062363733656430 38393963646563333732353531653239643330326539643538323164343934356166343034316565
37643339326661653031376265646132623162373562393437373437313732396537383939333666 33346431333735636537613930383331393265313962626234363237373562313231393061326439
62353036316633306666313461663033303830393765396131643035353730383931646239663935 64363765323935316661353531366165343139633963336139313737306332613364643031666161
32626461316364386135303761383837613063336466363162323332663764616464373565383231 30386362643930316265616564306336633133303166363665333462316265313364393939306162
61346463336566346533326535376439643133613762383633396131323632356533636139336365 31303639313933356337386134623934663461643161306666633261653538633232343036653833
62393838316634623932643034376631333539343965383436613364643962363834346337353334 66316466636233343136393765636333353230353738313833333265663238303730313936326664
32656439366439313734353963343133333533653839613632323338336131373566613835393536 38373239353162363438323964333030666563346161643437326335666162356264396135393532
31663433616334373432376531346435336530303936356461303163646463613661643161313661 63363862373136346532653734336335616132386237303031363433663132343861633937386130
66663866343565616631616338353737356164353562366164383736346131666662623132333466 30633938616364303462303030303966303939633066393264303462393730363233373937356439
39383865653631373232393433663430643961646265386166333137643966303834363262373636 36663533376232663737613734653532313136343939663539373866333638396266666163383864
62396434373363353636376133666133663162653265313139313732353639336232333862643036 63613532393334373539346338616163383637633237666234613437663966653733616361353830
64386231336561396537326139346566306434633934343038663165396665363032383466633662 61656666376133363330633863346637376266343134633037313132313361366638616261363839
62336163633964363435386630343966333162333730336138333239646631633132663931376462 39393062396237666333303937363536346561343763663133323236393037383532396465336138
33663139356261313065376636613930353735396131306538306664646135636336643032623131 35613463356532376534386433626337613030343266353332306462306463336336343830666138
38346264333331353633326535326431626563323036313665643337353563333339646430386564 33656138363837633337393865643633623261613335366263643162663637623636666162653632
31613435383036313430316366323636663735326336393338353835323861333564363832656462 30626238616266323332616234393838343330663662393433366630393566316336636530303165
35336435623261326363633033316130393062616339353263643062633331646137376135656365 38343665623437356636643236393734396264356632326133623264633862633333626330336663
35636139336564346164616235616431326531333433646330386134323932373339646536356464 61376263656665653731636133316161653635323138303866623862303065366232633736623336
66343533326534326165323564663533653666633035343163633832393361336462343937623165 39656666386435343062656138313061616661313966326432663236626631316162623961616636
62383931326630363036396333313931393836366439653433623165666166356338653364336534 35343939613262303066626537396164616666316265643065373638663436643961336138313862
35333936653833386163633738326164386166613561333530633937343230363366333662666539 39666163646538356338356631346534633139643636393866646462646533363265663234633761
39333361633933663735303438663239303536363433313962643137386533633539326365383765 65363661336138353239656165393836386134666331663036653132306433343764643666306333
37636538386339333935386132353265353031643662616330316463623661663738353433313830 33623661626565633333306337303263633335386632386330353730316436313931326164363862
36373963633166333464653338343830373063323536383364393033393235326639613662343737 37616265653161633632353865346639653961653836353962303762336535666266386535363165
38663362636331343061646465313237313431373433353361353265333766633463353632646536 39653138646663376634323131613463333035326639313266613830616431316131383464353533
31323231306138323031396630656538363930373439336234343963616334363632653738316465 62656634346637636164626461613137303461633761336232373133653532323566303136663030
63653938373830336362313238656266613362636634616537653863336132343931616262396130 30633337346534636566343934306662356238396365306563336666623435353731613136333036
66393239303866656232653832343132366537333537343635666563343639323433383163613335 36343436373932323265306639363761353364383635333136366231373166613861633032343233
39613533376634316133633430303535306266656333626264343733666335393661666561396633 61376338616630343639333964356162613332323835333730333135356665383431626138643534
39346265316137326465326635396362333565393133623637633132616232326263663662343137 66393966666465303763316230386538393863303063386564303165303962346139373338303436
33363733306135363361643031306265363733656362386666306334333035393839636533343363 39373032663538323532323766353864643338326561313564373562616430326264386362666532
35396638616636633639343930373136376339346162393061393765363837646365383866636131 36613132306462336631363035343732636465343562643430343035373961366566383130656165
33653465666239393133616232636231333332396138376332393664343364643835306530393238 64613938393265343037633161653937323933646637653036306532366237313838346361333932
34663237303530303837663535646263393931373531393039356336316561653130356262636562 34663565653264626137323239336532643262356166633665313761336162303635346666383863
38336362326639653237626634376334666565653036353236313634376364626338646538386536 61383930383033626337383366353766393536653135383062656639323361353539356232613736
38626636386466373566646166393963643164343536373236396138303532393161363335386638 63646235663363333333623463313961326533653236363938383765663439613832653039386436
32633032393737626363613463323366366637616361313537356136626661626633613739323338 38393734633536313731323437336332353564363564333736663037386530333639326338656561
35383963666431343566356562333234663936376562616638636261303466633539376334303331 66336637353238383231613666313261383234336531666132396230373931623363323832633064
39303834663234663063356233313962326664383839393832303462643636393034383434303465 39613036346166393936613939363865616135653830366435643538336365353333613831353962
64333635376135326333356435373734643430623736373234643335343130383066326436356664 36623537363434373137633063373934383439333462646361613737303239643834303535366138
63346663326364343634303930343338336139313864316165366232643537366635653764353763 34656465316431656461373737643537303936636539383934373831616438343965373765373535
31363863633261643263303433373330366161323166366462336332313135366338393334653764 63653164363731653030303466646539636361383664343763646163663238383435653035653666
66353733653137663835663731373364613030373334663061313433373861613665363236633130 63383165626365653261303834333234626534396333353231303261396361616233363334383336
65613965366636343465336533613438373466383737373366653965633437323562643966396431 33316462636133336132656364613439396131613565646565396365316238323962353462653736
39303033643438633762633263326132663466643438656366363431616237633031333936313831 64386139313266663963643962363133386133393166306163626632646463333363323830306164
30323930383233313032323638356333626230333764363662313662646536643839353032353462 35353936653137383761326132373739306163613764386531613032313235373331303530383633
30326166653937353130623133303533343934633565393831623033303234316330353432313266 64646533313434653734366233633535323564386431306538633666383661303038613330653832
30636536633933376365623665616262663236383731633633346232613366333137396139306363 34643463396137643034353439653334653836333161396130363637326339383363303037306330
35633336643266326335303261666666653536666630613639376336373237646134306462616537 61353635633334343432646461396439393439383639336139316161373737333961653731393333
33343561373162666332613634643837343566646161373065366637653135613632353334636363 61636164343838346365373736356161386430356533303331333838333732363233613931613863
63363232303963646530333366663862323264326536643337323266396566316233613630303637 66633662383466306332366563373865323861323833353238356563363635313463366333653432
66646366376466373931613734363931316230323063373666653062373364396433633762633762 65303839653963376566383737346231343663363363313332383365646363373737323839613564
38613933323733653238383935623230383562646563363833653838636165626365646537383639 34613362303335316363363661653639386538326337386537333765643161613961316531613563
33666535656363393562316336633439636138373365623431393965653765306138646234663938 38386564636637643762643830666138383361396233303339643665343261356462393830376662
65653133663663393731646337386535333261643932336132396237323930306136643534353930 32656334346536636536343263336565333234353831616565366538393661353561376538346334
65636438396432623034626561613137336138623265393064383034623863303166356138393564 61396135623230366433303932396130636331333263316333643861626564343330386636613063
37373164626634653662326234333539663735323464613334616130643937373730363263633366 32383061616435643736653264313839363232346332343565336464353138396339623533393237
31393937326432386165343338313031376565313866363731643534313233303064373935303538 38353632646565323735643462626239663736643033643231613464663866663262366632353434
31343832336230393636653432653162336361383963633766343461653466316337353931333363 37363866343239363131633464316133396462353336613962306332343563333962333934616330
63313137303564336630343937356564643763383764613362366634373362666465626334336539 3536356634376131633039373834376533633065303533653333
64366533376165306532343461613265366266383862323032333465336161663161376630316465
30306562666163646235656664653635366461366435663961623635383437663564356563346462
31636234663765623838333237393239373564366262613637363938653463396530613963643837
38636634376637366332623035313465393762653865623130336263343663303066366135616639
63333964356466613038303263366462346261353030646532366361393965306435613131316463
65366266376637323764643239323730366565633335666638666334663635373961303637383861
35313431646434656562333937663837393038386361616630626532636339306432353434656165
33663261383166386432383465666136376237346565303164363461666663346130346162316338
62373061353034316234303835663439396434343738303764376665336239626238386436386234
61306166383637366266393730323732386163366261393630336431633862353761343763363665
61323039396234393835303633363339373633653334343766653032313230343464326664356566
3462623830666664626633373966363866333337383730313066

View File

@@ -8,7 +8,7 @@ vault_git_work_email: "REPLACE_ME"
vault_git_work_gpg: "REPLACE_ME" vault_git_work_gpg: "REPLACE_ME"
vault_ikaros_authorized_ssh_keys: vault_ikaros_authorized_ssh_keys:
- "ssh-ed25519 REPLACE_ME" - "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_admin_password_hash: "REPLACE_WITH_A_SHADOW_COMPATIBLE_HASH"
vault_atlas_samba_password: "REPLACE_ME" vault_atlas_samba_password: "REPLACE_ME"
vault_atlas_immich_db_password: "REPLACE_ME" vault_atlas_immich_db_password: "REPLACE_ME"