--- # ---------- System setup ---------- - name: Set timezone copy: content: "{{ timezone }}" dest: /etc/timezone mode: '0644' register: tz_file - name: Apply timezone command: dpkg-reconfigure -f noninteractive tzdata when: tz_file.changed - name: Set hostname copy: content: "dawarich" dest: /etc/hostname mode: '0644' - name: Apply hostname command: hostname dawarich changed_when: false - name: Ensure hostname in /etc/hosts lineinfile: path: /etc/hosts regexp: '^127\.0\.1\.1' line: "127.0.1.1 dawarich" - name: Update apt cache apt: update_cache: true cache_valid_time: 3600 register: apt_cache_result failed_when: false changed_when: apt_cache_result.changed | default(false) - name: Install base packages apt: name: "{{ packages }}" state: present - name: Create user user: name: "{{ item.name }}" groups: "{{ item.groups }}" shell: "{{ item.shell }}" append: true loop: "{{ users }}" - name: Deploy SSH authorized keys authorized_key: user: cbalders key: "{{ item }}" state: present loop: "{{ ssh_authorized_keys }}" # ---------- Docker ---------- - name: Check if Docker is installed command: docker --version register: docker_check changed_when: false failed_when: false - name: Install Docker when: docker_check.rc != 0 block: - name: Download Docker install script get_url: url: https://get.docker.com dest: /tmp/get-docker.sh mode: '0755' - name: Run Docker install script command: /tmp/get-docker.sh - name: Remove install script file: path: /tmp/get-docker.sh state: absent - name: Ensure Docker service is running systemd: name: docker state: started enabled: true - name: Add cbalders to docker group user: name: cbalders groups: docker append: true # ---------- Docker registry mirror ---------- - name: Ensure /etc/docker exists file: path: /etc/docker state: directory mode: '0755' - name: Read existing daemon.json slurp: src: /etc/docker/daemon.json register: daemon_json_raw failed_when: false - name: Configure Docker registry mirror copy: content: "{{ ((daemon_json_raw.content | b64decode | from_json) if daemon_json_raw.content is defined else {}) | combine({'registry-mirrors': ['http://registry.lan.balders.ca:5000']}) | to_nice_json }}" dest: /etc/docker/daemon.json owner: root group: root mode: '0644' register: docker_daemon_config - name: Restart Docker if mirror config changed systemd: name: docker state: restarted when: docker_daemon_config.changed # ---------- Dawarich stack ---------- - name: Create app directories file: path: "{{ item.path }}" state: directory owner: "{{ item.owner | default('cbalders') }}" group: "{{ item.group | default('cbalders') }}" mode: '0755' loop: - { path: /opt/dawarich } # Postgis container runs as uid 70 (alpine 'postgres' user). Pre-chown # so the first boot doesn't fail with "directory is not empty / wrong # permissions" the way Nextcloud did (feedback_nextcloud_db_perms). - { path: /opt/dawarich/postgres, owner: '70', group: '70' } # Redis runs as uid 999 on bind volumes (feedback_redis_uid_999). - { path: /opt/dawarich/redis, owner: '999', group: '999' } # Dawarich Rails app writes uploads/cache here as uid 1000. - { path: /opt/dawarich/public } - { path: /opt/dawarich/watched } - { path: /opt/dawarich/storage } # solid_cache + solid_queue + solid_cable SQLite files (writable by the # rails uid inside the container — image runs as uid 1000). - { path: /opt/dawarich/db_data } - name: Deploy docker-compose template: src: docker-compose.yml.j2 dest: /opt/dawarich/docker-compose.yml owner: cbalders group: cbalders mode: '0640' register: compose_changed - name: Pull images command: docker compose pull args: chdir: /opt/dawarich changed_when: false - name: Recreate containers if config changed command: docker compose up -d --force-recreate args: chdir: /opt/dawarich when: compose_changed.changed changed_when: true - name: Ensure containers are running command: docker compose up -d args: chdir: /opt/dawarich when: not compose_changed.changed changed_when: false # Dawarich runs migrations on boot (entrypoint includes db:migrate). First # boot can take ~60-90s because the initial schema + PostGIS extension load # is heavy. Don't fail the play if it's not ready yet — Watchtower-driven # recreates will converge. - name: Wait for Dawarich to be healthy uri: url: "http://localhost:{{ dawarich_port }}/api/v1/health" method: GET register: dawarich_health until: dawarich_health.status is defined and dawarich_health.status == 200 retries: 30 delay: 5 ignore_errors: true # ---------- Weekly docker prune (Watchtower side-effect cleanup) ---------- - name: Deploy docker-prune systemd units copy: src: "{{ item }}" dest: "/etc/systemd/system/{{ item }}" owner: root group: root mode: '0644' loop: - docker-prune.service - docker-prune.timer notify: reload systemd - name: Enable docker-prune timer systemd: name: docker-prune.timer enabled: true state: started daemon_reload: true