diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index a844011..9ea46eb 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -78,23 +78,6 @@ no_log: true tags: [caddy] - # Immich API key for photo sync - - name: Fetch Immich API key - ansible.builtin.command: - cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get ifcuzuceejqcjx7k7zd2gdsjem --fields api_key --reveal - delegate_to: localhost - register: _immich_api_key - changed_when: false - no_log: true - check_mode: false - tags: [immich_sync] - - - name: Set Immich API key fact - ansible.builtin.set_fact: - immich_sync_api_key: "{{ _immich_api_key.stdout }}" - no_log: true - tags: [immich_sync] - roles: - role: alloy tags: alloy @@ -116,5 +99,3 @@ tags: plex_metrics - role: caddy tags: caddy - - role: immich_sync - tags: immich_sync diff --git a/ansible/roles/immich_sync/defaults/main.yml b/ansible/roles/immich_sync/defaults/main.yml deleted file mode 100644 index 81899bd..0000000 --- a/ansible/roles/immich_sync/defaults/main.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -# Immich server URL -immich_sync_url: "https://photos.ops.eblu.me" - -# Directory paths -immich_sync_export_dir: /Users/erichblume/Pictures/immich-export -immich_sync_log_dir: /Users/erichblume/Library/Logs -immich_sync_bin_dir: /Users/erichblume/bin - -# Schedule: hourly (StartInterval in seconds) -immich_sync_interval_seconds: 3600 - -# osxphotos export options -immich_sync_export_edited: true # Export edited versions -immich_sync_export_originals: true # Also export originals (as sidecars) -immich_sync_update_mode: true # Only export new/changed photos - -# immich-cli options -immich_sync_create_albums: true # Create albums from folder names -immich_sync_concurrency: 4 # Parallel uploads diff --git a/ansible/roles/immich_sync/handlers/main.yml b/ansible/roles/immich_sync/handlers/main.yml deleted file mode 100644 index 6408807..0000000 --- a/ansible/roles/immich_sync/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Reload immich-sync - ansible.builtin.shell: | - launchctl unload ~/Library/LaunchAgents/mcquack.eblume.immich-sync.plist 2>/dev/null || true - launchctl load ~/Library/LaunchAgents/mcquack.eblume.immich-sync.plist - changed_when: true diff --git a/ansible/roles/immich_sync/tasks/main.yml b/ansible/roles/immich_sync/tasks/main.yml deleted file mode 100644 index 3e0d6af..0000000 --- a/ansible/roles/immich_sync/tasks/main.yml +++ /dev/null @@ -1,55 +0,0 @@ ---- -# Note: osxphotos is installed via mise (pipx), not managed here. -# This role manages the sync wrapper script and scheduled LaunchAgent. -# -# Prerequisites: -# - osxphotos: Add to ~/.config/mise/config.toml on indri: -# [tools] -# "pipx:osxphotos" = "latest" -# Then run: mise install -# - Docker: Already installed on indri - -- name: Ensure export directory exists - ansible.builtin.file: - path: "{{ immich_sync_export_dir }}" - state: directory - mode: '0755' - -- name: Ensure bin directory exists - ansible.builtin.file: - path: "{{ immich_sync_bin_dir }}" - state: directory - mode: '0755' - -- name: Write Immich API key to secure file - ansible.builtin.copy: - content: "{{ immich_sync_api_key }}" - dest: ~/.immich-api-key - mode: '0600' - no_log: true - -- name: Deploy immich-sync wrapper script - ansible.builtin.template: - src: immich-sync.sh.j2 - dest: "{{ immich_sync_bin_dir }}/immich-sync.sh" - mode: '0755' - notify: Reload immich-sync - -- name: Deploy immich-sync LaunchAgent plist - ansible.builtin.template: - src: immich-sync.plist.j2 - dest: ~/Library/LaunchAgents/mcquack.eblume.immich-sync.plist - mode: '0644' - notify: Reload immich-sync - -- name: Check if immich-sync LaunchAgent is loaded - ansible.builtin.command: launchctl list mcquack.eblume.immich-sync - register: immich_sync_launchctl_check - changed_when: false - failed_when: false - -- name: Load immich-sync LaunchAgent if not loaded - ansible.builtin.command: launchctl load ~/Library/LaunchAgents/mcquack.eblume.immich-sync.plist - when: immich_sync_launchctl_check.rc != 0 - changed_when: true - failed_when: false diff --git a/ansible/roles/immich_sync/templates/immich-sync.plist.j2 b/ansible/roles/immich_sync/templates/immich-sync.plist.j2 deleted file mode 100644 index 0c20474..0000000 --- a/ansible/roles/immich_sync/templates/immich-sync.plist.j2 +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - KeepAlive - - Label - mcquack.eblume.immich-sync - EnvironmentVariables - - PATH - /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin - HOME - /Users/erichblume - - ProgramArguments - - {{ immich_sync_bin_dir }}/immich-sync.sh - - RunAtLoad - - StandardErrorPath - {{ immich_sync_log_dir }}/mcquack.immich-sync.err.log - StandardOutPath - {{ immich_sync_log_dir }}/mcquack.immich-sync.out.log - StartInterval - {{ immich_sync_interval_seconds }} - - diff --git a/ansible/roles/immich_sync/templates/immich-sync.sh.j2 b/ansible/roles/immich_sync/templates/immich-sync.sh.j2 deleted file mode 100644 index 1895a12..0000000 --- a/ansible/roles/immich_sync/templates/immich-sync.sh.j2 +++ /dev/null @@ -1,150 +0,0 @@ -#!/bin/bash -# {{ ansible_managed }} -# -# Immich photo sync script -# Exports photos from macOS Photos Library and uploads to Immich -# -# Prerequisites: -# - osxphotos (installed via mise/pipx) -# - Docker (for immich-cli) -# - API key at ~/.immich-api-key - -set -euo pipefail - -# Explicit paths for LaunchAgent context (no PATH inheritance) -MISE=/opt/homebrew/opt/mise/bin/mise -DOCKER=/usr/local/bin/docker - -EXPORT_DIR="{{ immich_sync_export_dir }}" -IMMICH_URL="{{ immich_sync_url }}" -API_KEY_FILE="$HOME/.immich-api-key" -LOCKFILE="$HOME/.immich-sync.lock" -LOG_PREFIX="[immich-sync]" - -log() { - echo "$LOG_PREFIX $(date '+%Y-%m-%d %H:%M:%S') $*" -} - -error() { - echo "$LOG_PREFIX $(date '+%Y-%m-%d %H:%M:%S') ERROR: $*" >&2 -} - -# Lockfile management to prevent concurrent runs -acquire_lock() { - if [[ -f "$LOCKFILE" ]]; then - local lock_pid lock_time - lock_pid=$(cat "$LOCKFILE" 2>/dev/null | head -1) - lock_time=$(stat -f %Sm -t '%Y-%m-%d %H:%M:%S' "$LOCKFILE" 2>/dev/null || echo "unknown") - - # Check if the process is still running - if [[ -n "$lock_pid" ]] && kill -0 "$lock_pid" 2>/dev/null; then - error "Another sync is already running (PID: $lock_pid, started: $lock_time)" - error "Lockfile: $LOCKFILE" - error "If this is stale, remove the lockfile manually: rm $LOCKFILE" - exit 0 # Exit cleanly so LaunchAgent doesn't report failure - else - log "WARNING: Found stale lockfile from PID $lock_pid (process not running), removing it" - rm -f "$LOCKFILE" - fi - fi - - # Create lockfile with our PID - echo $$ > "$LOCKFILE" - log "Acquired lock (PID: $$)" -} - -release_lock() { - if [[ -f "$LOCKFILE" ]]; then - rm -f "$LOCKFILE" - log "Released lock" - fi -} - -# Ensure lock is released on exit (success or failure) -trap release_lock EXIT - -# Acquire lock before doing anything else -acquire_lock - -# Check prerequisites -if [[ ! -x "$MISE" ]]; then - error "mise not found at $MISE" - exit 1 -fi - -if [[ ! -x "$DOCKER" ]]; then - error "Docker not found at $DOCKER" - exit 1 -fi - -if [[ ! -f "$API_KEY_FILE" ]]; then - error "Immich API key not found at $API_KEY_FILE" - exit 1 -fi - -IMMICH_API_KEY=$(cat "$API_KEY_FILE") - -# Ensure export directory exists -mkdir -p "$EXPORT_DIR" - -log "Starting photo export from Photos Library to $EXPORT_DIR" - -# Export photos using osxphotos -# --directory: export directory structure by date -# --update: only export new/changed photos (incremental) -# --exiftool: write metadata to EXIF tags -# --edited-suffix: suffix for edited versions (default exports both original and edited) -# --download-missing: download from iCloud if needed -OSXPHOTOS_ARGS=( - export "$EXPORT_DIR" - --directory "{created.year}/{created.mm}-{created.mon}" -{% if immich_sync_update_mode %} - --update -{% endif %} - --exiftool - --download-missing - --edited-suffix "_edited" -{% if not immich_sync_export_edited %} - --skip-edited -{% endif %} -{% if not immich_sync_export_originals %} - --skip-original-if-edited -{% endif %} -) - -log "Running: mise x -- osxphotos ${OSXPHOTOS_ARGS[*]}" -if ! "$MISE" x -- osxphotos "${OSXPHOTOS_ARGS[@]}"; then - error "osxphotos export failed" - exit 1 -fi - -log "Photo export complete" -log "Starting upload to Immich at $IMMICH_URL" - -# Upload to Immich using immich-cli in Docker -# -r: recursive upload -# -a: create albums from folder names -# --concurrency: parallel uploads -IMMICH_CLI_ARGS=( - upload - --recursive -{% if immich_sync_create_albums %} - --album -{% endif %} - --concurrency {{ immich_sync_concurrency }} - /import -) - -log "Running: docker run immich-cli ${IMMICH_CLI_ARGS[*]}" -if ! "$DOCKER" run --rm \ - -v "$EXPORT_DIR:/import:ro" \ - -e IMMICH_INSTANCE_URL="$IMMICH_URL" \ - -e IMMICH_API_KEY="$IMMICH_API_KEY" \ - ghcr.io/immich-app/immich-cli:latest \ - "${IMMICH_CLI_ARGS[@]}"; then - error "immich-cli upload failed" - exit 1 -fi - -log "Upload to Immich complete" -log "Sync finished successfully"