mirror of
https://github.com/fscotto/infra.git
synced 2026-10-03 13:29:58 +00:00
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:
committed by
GitHub
parent
9e76309833
commit
4bd6aafb53
41
AGENTS.md
41
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`
|
`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.
|
||||||
|
|||||||
15
README.it.md
15
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
|
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,
|
||||||
|
|||||||
22
README.md
22
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
|
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`.
|
||||||
|
|||||||
@@ -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('') }}"
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 }}"
|
||||||
|
|||||||
@@ -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 }}"
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
165
ansible/roles/profile_atlas/tasks/icloudpd.yml
Normal file
165
ansible/roles/profile_atlas/tasks/icloudpd.yml
Normal 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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
14
ansible/roles/profile_atlas/templates/atlas-icloudpd.conf.j2
Normal file
14
ansible/roles/profile_atlas/templates/atlas-icloudpd.conf.j2
Normal 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
|
||||||
@@ -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
|
||||||
163
docs/atlas-icloudpd-migration.md
Normal file
163
docs/atlas-icloudpd-migration.md
Normal 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.
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user