# update_bettershift.yml --- - name: Update BetterShift hosts: pve2_vm gather_facts: false become: true vars: compose_local_dir: "{{ playbook_dir }}/docker-compose" compose_remote_base: "/home/howard/.ansible-compose/docker-compose" bettershift_project: bettershift bettershift_compose_filename: docker-compose-bettershift.yml bettershift_service: bettershift bettershift_port: 3067 bettershift_data_dir: /data/compose/bettershift/data bettershift_version: v3.3.0 environment: BETTERSHIFT_VERSION: "{{ bettershift_version }}" tasks: - name: Check existing database and env file ansible.builtin.stat: path: "{{ item }}" loop: - "{{ bettershift_data_dir }}/sqlite.db" - "{{ compose_remote_base }}/.env.bettershift" register: bettershift_files - name: Refuse missing or empty files ansible.builtin.assert: that: - item.stat.isreg | default(false) - item.stat.size | default(0) > 0 fail_msg: "Missing or empty file: {{ item.item }}" loop: "{{ bettershift_files.results }}" loop_control: label: "{{ item.item }}" - name: Check database before changing deployment ansible.builtin.command: argv: - python3 - -c - | import pathlib, sqlite3, sys path = pathlib.Path(sys.argv[1]) with sqlite3.connect(path.as_uri() + '?mode=ro', uri=True) as src: with sqlite3.connect(':memory:') as db: src.backup(db) integrity = db.execute('PRAGMA integrity_check').fetchall() errors = db.execute('PRAGMA foreign_key_check').fetchall() print('Integrity:', integrity, 'Foreign key violations:', errors) known_errors = { ('audit_logs', 1, 'user', 0), ('audit_logs', 2, 'user', 0), ('audit_logs', 3, 'user', 0), ('audit_logs', 4, 'user', 0), ('audit_logs', 5, 'user', 0), ('calendars', 1, 'user', 0), } missing_user = 'RnqG0DGnnCOVgL3iWRRUAvHlMhLcdm6Y' unexpected = [] for error in errors: if error not in known_errors: unexpected.append(error) continue table, rowid, _, _ = error column = 'owner_id' if table == 'calendars' else 'user_id' value = db.execute( f'SELECT "{column}" FROM "{table}" WHERE rowid = ?', (rowid,) ).fetchone() if value != (missing_user,): unexpected.append(error) elif table == 'calendars': calendar = db.execute( 'SELECT id FROM calendars WHERE rowid = ?', (rowid,) ).fetchone() if calendar != ('60a3f0e7-c8f6-4f53-92f9-7088dff4656a',): unexpected.append(error) print('Unexpected foreign key violations:', unexpected) sys.exit(0 if integrity == [('ok',)] and not unexpected else 1) - "{{ bettershift_data_dir }}/sqlite.db" changed_when: false - name: Upload BetterShift compose file ansible.builtin.copy: src: "{{ compose_local_dir }}/{{ bettershift_compose_filename }}" dest: "{{ compose_remote_base }}/{{ bettershift_compose_filename }}" mode: '0644' backup: true - name: Pull image without starting it community.docker.docker_compose_v2_pull: project_name: "{{ bettershift_project }}" project_src: "{{ compose_remote_base }}" files: ["{{ bettershift_compose_filename }}"] - name: Create private backup directory ansible.builtin.tempfile: state: directory path: /data/compose/bettershift prefix: backup-before-upgrade- register: bettershift_backup - name: Show backup location ansible.builtin.debug: msg: "Backup: {{ bettershift_backup.path }}" - name: Stop service and create a verified backup block: - name: Stop BetterShift ansible.builtin.command: argv: [docker, stop, --timeout, '60', bettershift] changed_when: true - name: Copy data and compare files ansible.builtin.shell: | set -eu test "$(docker inspect --format '{% raw %}{{.State.Running}}{% endraw %}' bettershift)" = false cp -a -- "$DATA_DIR" "$BACKUP_DIR/data" diff -qr -- "$DATA_DIR" "$BACKUP_DIR/data" environment: DATA_DIR: "{{ bettershift_data_dir }}" BACKUP_DIR: "{{ bettershift_backup.path }}" changed_when: true rescue: - name: Restart original container if backup fails ansible.builtin.command: argv: [docker, start, bettershift] changed_when: true - name: Abort update ansible.builtin.fail: msg: Backup failed. Original container restarted; update aborted. - name: Recreate BetterShift once community.docker.docker_compose_v2: project_name: "{{ bettershift_project }}" project_src: "{{ compose_remote_base }}" files: ["{{ bettershift_compose_filename }}"] services: ["{{ bettershift_service }}"] state: present pull: never recreate: always - name: Check BetterShift HTTP endpoint ansible.builtin.uri: url: "http://127.0.0.1:{{ bettershift_port }}/" status_code: [200, 307, 308] follow_redirects: none register: bettershift_http retries: 30 delay: 3 until: bettershift_http.status | default(0) in [200, 307, 308] changed_when: false