# update_immich.yml - name: Update Immich hosts: pve2_vm gather_facts: false vars: # Compose sync (controller -> target) compose_local_dir: "{{ playbook_dir }}/docker-compose" compose_remote_base: "/home/{{ ansible_user }}/.ansible-compose" compose_remote_dir: "{{ compose_remote_base }}/docker-compose" compose_remote_archive: "{{ compose_remote_base }}/docker-compose.tar.gz" # Immich settings immich_project: immich immich_port: 2283 immich_compose_files: - docker-compose-immich.yml - docker-compose-immich.override.yml # Remote .env handling immich_env_backup: "{{ compose_remote_base }}/immich.env.backup" tasks: - name: Ensure remote base directory exists ansible.builtin.file: path: "{{ compose_remote_base }}" state: directory mode: "0755" # --- Preserve .env across staging refresh (because .env is usually not in git) --- - name: Backup existing Immich .env on remote (if present) ansible.builtin.copy: src: "{{ compose_remote_dir }}/.env" dest: "{{ immich_env_backup }}" remote_src: true mode: "0600" ignore_errors: true - name: Create local archive of docker-compose directory (controller) ansible.builtin.archive: path: "{{ compose_local_dir }}/" dest: "/tmp/docker-compose.tar.gz" format: gz delegate_to: localhost run_once: true - name: Upload archive to remote host ansible.builtin.copy: src: "/tmp/docker-compose.tar.gz" dest: "{{ compose_remote_archive }}" mode: "0644" - name: Recreate remote compose directory ansible.builtin.file: path: "{{ compose_remote_dir }}" state: absent - name: Ensure remote compose directory exists ansible.builtin.file: path: "{{ compose_remote_dir }}" state: directory mode: "0755" - name: Extract archive on remote host ansible.builtin.unarchive: src: "{{ compose_remote_archive }}" dest: "{{ compose_remote_dir }}" remote_src: true - name: Restore Immich .env on remote (if backup exists) ansible.builtin.copy: src: "{{ immich_env_backup }}" dest: "{{ compose_remote_dir }}/.env" remote_src: true mode: "0600" ignore_errors: true - name: Ensure Immich .env exists (reconstruct from running containers if missing) ansible.builtin.command: argv: - bash - -lc - | set -euo pipefail cd "{{ compose_remote_dir }}" if [ -f .env ]; then exit 0 fi python3 - <<'PY' import json import subprocess from pathlib import Path env_path = Path(".env") def run(cmd): p = subprocess.run(cmd, capture_output=True, text=True) return p.returncode, p.stdout, p.stderr rc, out, err = run(["bash", "-lc", "command docker inspect immich_postgres immich_server"]) if rc != 0 or not out.strip(): print("ERROR: .env is missing and cannot inspect running containers (immich_postgres/immich_server).", flush=True) print("Create the .env on the VM or ensure the containers exist.", flush=True) raise SystemExit(1) data = json.loads(out) by_name = {} for c in data: name = (c.get("Name") or "").lstrip("/") by_name[name] = c pg = by_name.get("immich_postgres") srv = by_name.get("immich_server") if not pg or not srv: print("ERROR: Could not find immich_postgres and immich_server in docker inspect output.", flush=True) raise SystemExit(1) def env_map(container): m = {} for kv in (container.get("Config", {}).get("Env") or []): if "=" in kv: k, v = kv.split("=", 1) m[k] = v return m def find_mount_source(container, dest): for m in (container.get("Mounts") or []): if m.get("Destination") == dest: return m.get("Source") return "" pg_env = env_map(pg) db_user = pg_env.get("POSTGRES_USER", "") db_pass = pg_env.get("POSTGRES_PASSWORD", "") db_name = pg_env.get("POSTGRES_DB", "") db_data = find_mount_source(pg, "/var/lib/postgresql/data") upload_loc = find_mount_source(srv, "/usr/src/app/upload") immich_version = "" image = (srv.get("Config", {}).get("Image") or "") if ":" in image and "@" not in image: immich_version = image.rsplit(":", 1)[-1] elif ":" in image and "@" in image: immich_version = image.split("@", 1)[0].rsplit(":", 1)[-1] missing = [] for k, v in [ ("DB_USERNAME", db_user), ("DB_PASSWORD", db_pass), ("DB_DATABASE_NAME", db_name), ("DB_DATA_LOCATION", db_data), ("UPLOAD_LOCATION", upload_loc), ]: if not v: missing.append(k) if missing: print("ERROR: Could not reconstruct these .env values from containers: " + ", ".join(missing), flush=True) raise SystemExit(1) lines = [ f"UPLOAD_LOCATION={upload_loc}", f"DB_DATA_LOCATION={db_data}", f"DB_USERNAME={db_user}", f"DB_PASSWORD={db_pass}", f"DB_DATABASE_NAME={db_name}", ] if immich_version: lines.append(f"IMMICH_VERSION={immich_version}") env_path.write_text("\n".join(lines) + "\n", encoding="utf-8") print("Created .env from running containers.", flush=True) PY - name: Pull latest Immich images community.docker.docker_compose_v2: project_name: "{{ immich_project }}" project_src: "{{ compose_remote_dir }}" files: "{{ immich_compose_files }}" pull: always - name: Recreate Immich stack community.docker.docker_compose_v2: project_name: "{{ immich_project }}" project_src: "{{ compose_remote_dir }}" files: "{{ immich_compose_files }}" state: present recreate: always - name: Wait for Immich port ansible.builtin.wait_for: host: 127.0.0.1 port: "{{ immich_port }}" timeout: 120 - name: Check Immich HTTP endpoint ansible.builtin.uri: url: "http://127.0.0.1:{{ immich_port }}/api/server/ping" status_code: 200 return_content: true