--- - name: verify operating system fail: msg: Sorry, only Debian and Ubuntu supported at the moment. when: not(ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu') - name: install base dependencies apt: update_cache: yes pkg: # paperless-ng - python3-pip - python3-dev - fonts-liberation - imagemagick - optipng - gnupg - libpq-dev - libmagic-dev - mime-support # OCRmyPDF - unpaper - ghostscript - icc-profiles-free - qpdf - liblept5 - libxml2 - pngquant - zlib1g - tesseract-ocr # dev - sudo - build-essential - python3-setuptools - python3-wheel # upstream virtualenv in Ubuntu 20.04 is broken # https://github.com/pypa/virtualenv/issues/1873 - name: install python virtualenv pip: name: virtualenv extra_args: --upgrade - name: install ocr languages apt: pkg: "{{ paperlessng_ocr_languages | map('regex_replace', '^(.*)$', 'tesseract-ocr-\\1') | map('replace', '_', '-') | list }}" - name: set up notesalexp repository key (for jbig2enc) apt_key: url: https://notesalexp.org/debian/alexp_key.asc state: present when: paperlessng_use_jbig2enc - name: set up notesalexp repository (for jbig2enc) apt_repository: repo: "deb https://notesalexp.org/debian/{{ ansible_distribution_release }}/ {{ ansible_distribution_release }} main" state: present when: paperlessng_use_jbig2enc - name: set up notesalexp repository pinning copy: content: | Package: * Pin: release o=notesalexp.org Pin-Priority: 1 Package: jbig2enc Pin: release o=notesalexp.org Pin-Priority: 500 dest: /etc/apt/preferences.d/notesalexp when: paperlessng_use_jbig2enc - name: install jbig2enc apt: pkg: jbig2enc update_cache: yes when: paperlessng_use_jbig2enc - name: install redis apt: pkg: redis-server when: paperlessng_redis_host == 'localhost' or paperlessng_redis_host == '127.0.0.1' - name: enable redis systemd: name: redis-server enabled: yes masked: no state: started when: paperlessng_redis_host == 'localhost' or paperlessng_redis_host == '127.0.0.1' - name: create paperless system group group: name: "{{ paperlessng_system_group }}" - name: create paperless system user user: name: "{{ paperlessng_system_user }}" groups: - "{{ paperlessng_system_group }}" shell: /usr/sbin/nologin # GNUPG_HOME required due to paperless db.py create_home: yes - block: - name: get latest release version uri: url: https://api.github.com/repos/jonaswinkler/paperless-ng/releases/latest method: GET register: latest_release - name: parse latest release version set_fact: paperlessng_version: "{{ latest_release.json['tag_name'] }}" when: paperlessng_version == "latest" - name: check if version is ref fail: msg: "Specifying `paperlessng_version` as git ref may not work as expected!" ignore_errors: True # Output failure (as warning), but don't consider play failed when: paperlessng_version.startswith('refs/') - block: - name: sanitize version string set_fact: paperlessng_version: "{{ paperlessng_version | regex_replace('^ng-(\\d+\\.\\d+\\.\\d+)$', '\\1') }}" - name: get tag data uri: url: https://api.github.com/repos/jonaswinkler/paperless-ng/tags method: GET register: tags - name: get commit for target tag set_fact: paperlessng_commit: "{{ tags.json | json_query('[?name==`ng-' + paperlessng_version +'`] | [0].commit.sha') }}" when: paperlessng_version | regex_search("^(ng-)?(\d+\.\d+\.\d+)$") - block: - name: check if version is branch uri: url: "https://api.github.com/repos/jonaswinkler/paperless-ng/branches/{{ paperlessng_version }}" method: GET status_code: [200, 404] register: branch - name: get commit for target branch set_fact: paperlessng_commit: "{{ branch.json | json_query('commit.sha') }}" when: branch.status == 200 - block: - name: check if version is commit-or-ref uri: url: "https://api.github.com/repos/jonaswinkler/paperless-ng/commits/{{ paperlessng_version }}" method: GET status_code: [200, 404, 422] register: commit - name: get commit for target commit-or-ref set_fact: paperlessng_commit: "{{ commit.json | json_query('sha') }}" when: commit.status == 200 - name: fail fail: msg: "Can not determine commit from `paperlessng_version=={{ paperlessng_version }}`!" when: commit.status != 200 when: branch.status == 404 when: not(paperlessng_version | regex_search("^(ng-)?(\d+\.\d+\.\d+)$")) - name: check for paperless-ng installation command: cmd: "cat {{ paperlessng_directory }}/.installed_version" changed_when: '"No such file or directory" in paperlessng_current_commit.stderr or paperlessng_current_commit.stdout != paperlessng_commit | string' failed_when: false ignore_errors: yes register: paperlessng_current_commit - name: register current state set_fact: fresh_installation: '{{ "No such file or directory" in paperlessng_current_commit.stderr }}' update_installation: '{{ "No such file or directory" not in paperlessng_current_commit.stderr and paperlessng_current_commit.stdout != paperlessng_commit | string }}' reconfigure_only: "{{ paperlessng_current_commit.stdout == paperlessng_commit | string }}" - block: - name: backup current paperless-ng installation copy: src: "{{ paperlessng_directory }}" remote_src: yes dest: "{{ paperlessng_directory }}-{{ ansible_date_time.iso8601 }}/" - name: remove current paperless sources file: path: "{{ paperlessng_directory }}/{{ item }}" state: absent with_items: - docker - docs - scripts - src - static when: update_installation - block: - name: create paperless-ng directory and set permissions file: path: "{{ paperlessng_directory }}" state: directory owner: "{{ paperlessng_system_user }}" group: "{{ paperlessng_system_group }}" mode: "750" - name: create temporary directory become: yes become_user: "{{ paperlessng_system_user }}" tempfile: state: directory path: "{{ paperlessng_directory }}" register: tempdir - name: check if version is available as release archive uri: url: "https://github.com/jonaswinkler/paperless-ng/releases/download/ng-{{ paperlessng_version }}/paperless-ng-{{ paperlessng_version }}.tar.xz" method: GET status_code: [200, 404] register: release_archive - name: install paperless-ng from source include_tasks: install-source.yml when: release_archive.status == 404 - name: install paperless-ng from release archive include_tasks: install-release.yml when: release_archive.status == 200 - name: change owner and permissions of paperless-ng command: cmd: "{{ item }}" warn: false with_items: - "chown -R {{ paperlessng_system_user }}:{{ paperlessng_system_group }} {{ tempdir.path }}" - "find {{ tempdir.path }} -type d -exec chmod 0750 {} ;" - "find {{ tempdir.path }} -type f -exec chmod 0640 {} ;" - name: move paperless-ng command: cmd: "cp -a {{ tempdir.path }}/paperless-ng/. {{ paperlessng_directory }}" - name: store commit hash of installed version copy: content: "{{ paperlessng_commit }}" dest: "{{ paperlessng_directory }}/.installed_version" owner: "{{ paperlessng_system_user }}" group: "{{ paperlessng_system_group }}" mode: "0440" - name: remove temporary directory file: path: "{{ tempdir.path }}" state: absent when: not reconfigure_only - name: create paperless-ng directories and set permissions file: path: "{{ item }}" state: directory owner: "{{ paperlessng_system_user }}" group: "{{ paperlessng_system_group }}" mode: "750" with_items: - "{{ paperlessng_consumption_dir }}" - "{{ paperlessng_data_dir }}" - "{{ paperlessng_media_root }}" - "{{ paperlessng_staticdir }}" - name: rename initial config command: cmd: "mv -f {{ paperlessng_directory }}/paperless.conf {{ paperlessng_directory }}/paperless.conf.template" removes: "{{ paperlessng_directory }}/paperless.conf" - name: configure paperless-ng lineinfile: path: "{{ paperlessng_directory }}/paperless.conf.template" regexp: "^#?{{ item.regexp }}=" line: "{{ item.line }}" with_items: # Required services - regexp: PAPERLESS_REDIS line: "PAPERLESS_REDIS=redis://{{ paperlessng_redis_host }}:{{ paperlessng_redis_port }}" # Paths and folders - regexp: PAPERLESS_CONSUMPTION_DIR line: "PAPERLESS_CONSUMPTION_DIR={{ paperlessng_consumption_dir }}" - regexp: PAPERLESS_DATA_DIR line: "PAPERLESS_DATA_DIR={{ paperlessng_data_dir }}" - regexp: PAPERLESS_MEDIA_ROOT line: "PAPERLESS_MEDIA_ROOT={{ paperlessng_media_root }}" - regexp: PAPERLESS_STATICDIR line: "PAPERLESS_STATICDIR={{ paperlessng_staticdir }}" - regexp: PAPERLESS_FILENAME_FORMAT line: "PAPERLESS_FILENAME_FORMAT={{ paperlessng_filename_format }}" - regexp: PAPERLESS_LOGGING_DIR line: "PAPERLESS_LOGGING_DIR={{ paperlessng_logging_dir }}" # Hosting & Security - regexp: PAPERLESS_SECRET_KEY line: "PAPERLESS_SECRET_KEY={{ paperlessng_secret_key }}" - regexp: PAPERLESS_ALLOWED_HOSTS line: "PAPERLESS_ALLOWED_HOSTS={{ paperlessng_allowed_hosts }}" - regexp: PAPERLESS_CORS_ALLOWED_HOSTS line: "PAPERLESS_CORS_ALLOWED_HOSTS={{ paperlessng_cors_allowed_hosts }}" - regexp: PAPERLESS_FORCE_SCRIPT_NAME line: "PAPERLESS_FORCE_SCRIPT_NAME={{ paperlessng_force_script_name }}" - regexp: PAPERLESS_STATIC_URL line: "PAPERLESS_STATIC_URL={{ paperlessng_static_url }}" - regexp: PAPERLESS_AUTO_LOGIN_USERNAME line: "PAPERLESS_AUTO_LOGIN_USERNAME={{ paperlessng_auto_login_username }}" - regexp: PAPERLESS_COOKIE_PREFIX line: "PAPERLESS_COOKIE_PREFIX={{ paperlessng_cookie_prefix }}" - regexp: PAPERLESS_ENABLE_HTTP_REMOTE_USER line: "PAPERLESS_ENABLE_HTTP_REMOTE_USER={{ paperlessng_enable_http_remote_user }}" # OCR settings - regexp: PAPERLESS_OCR_LANGUAGE line: "PAPERLESS_OCR_LANGUAGE={{ paperlessng_ocr_languages | join('+') | replace('-','_') }}" - regexp: PAPERLESS_OCR_MODE line: "PAPERLESS_OCR_MODE={{ paperlessng_ocr_mode }}" - regexp: PAPERLESS_OCR_CLEAN line: "PAPERLESS_OCR_CLEAN={{ paperlessng_ocr_clean }}" - regexp: PAPERLESS_OCR_DESKEW line: "PAPERLESS_OCR_DESKEW={{ paperlessng_ocr_deskew }}" - regexp: PAPERLESS_OCR_ROTATE_PAGES line: "PAPERLESS_OCR_ROTATE_PAGES={{ paperlessng_ocr_rotate_pages }}" - regexp: PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD line: "PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD={{ paperlessng_ocr_rotate_pages_threshold }}" - regexp: PAPERLESS_OCR_OUTPUT_TYPE line: "PAPERLESS_OCR_OUTPUT_TYPE={{ paperlessng_ocr_output_type }}" - regexp: PAPERLESS_OCR_PAGES line: "PAPERLESS_OCR_PAGES={{ paperlessng_ocr_pages }}" - regexp: PAPERLESS_OCR_IMAGE_DPI line: "PAPERLESS_OCR_IMAGE_DPI={{ paperlessng_ocr_image_dpi }}" - regexp: PAPERLESS_OCR_USER_ARGS line: "PAPERLESS_OCR_USER_ARGS={{ paperlessng_ocr_user_args | combine({'jbig2_lossy': true} if paperlessng_big2enc_lossy else {}) | to_json }}" # Tika settings - regexp: PAPERLESS_TIKA_ENABLED line: "PAPERLESS_TIKA_ENABLED={{ paperlessng_tika_enabled }}" - regexp: PAPERLESS_TIKA_ENDPOINT line: "PAPERLESS_TIKA_ENDPOINT={{ paperlessng_tika_endpoint }}" - regexp: PAPERLESS_TIKA_GOTENBERG_ENDPOINT line: "PAPERLESS_TIKA_GOTENBERG_ENDPOINT={{ paperlessng_tika_gotenberg_endpoint }}" # Software tweaks - regexp: PAPERLESS_TIME_ZONE line: "PAPERLESS_TIME_ZONE={{ paperlessng_time_zone }}" - regexp: PAPERLESS_CONSUMER_POLLING line: "PAPERLESS_CONSUMER_POLLING={{ paperlessng_consumer_polling }}" - regexp: PAPERLESS_CONSUMER_DELETE_DUPLICATES line: "PAPERLESS_CONSUMER_DELETE_DUPLICATES={{ paperlessng_consumer_delete_duplicates }}" - regexp: PAPERLESS_CONSUMER_RECURSIVE line: "PAPERLESS_CONSUMER_RECURSIVE={{ paperlessng_consumer_recursive }}" - regexp: PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS line: "PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS={{ paperlessng_consumer_subdirs_as_tags }}" - regexp: PAPERLESS_CONVERT_MEMORY_LIMIT line: "PAPERLESS_CONVERT_MEMORY_LIMIT={{ paperlessng_convert_memory_limit }}" - regexp: PAPERLESS_CONVERT_TMPDIR line: "PAPERLESS_CONVERT_TMPDIR={{ paperlessng_convert_tmpdir }}" - regexp: PAPERLESS_OPTIMIZE_THUMBNAILS line: "PAPERLESS_OPTIMIZE_THUMBNAILS={{ paperlessng_optimize_thumbnails }}" - regexp: PAPERLESS_PRE_CONSUME_SCRIPT line: "PAPERLESS_PRE_CONSUME_SCRIPT={{ paperlessng_pre_consume_script }}" - regexp: PAPERLESS_POST_CONSUME_SCRIPT line: "PAPERLESS_POST_CONSUME_SCRIPT={{ paperlessng_post_consume_script }}" - regexp: PAPERLESS_FILENAME_DATE_ORDER line: "PAPERLESS_FILENAME_DATE_ORDER={{ paperlessng_filename_date_order }}" - regexp: PAPERLESS_THUMBNAIL_FONT_NAME line: "PAPERLESS_THUMBNAIL_FONT_NAME={{ paperlessng_thumbnail_font_name }}" - regexp: PAPERLESS_IGNORE_DATES line: "PAPERLESS_IGNORE_DATES={{ paperlessng_ignore_dates }}" no_log: yes - name: configure paperless-ng database [sqlite] lineinfile: path: "{{ paperlessng_directory }}/paperless.conf.template" regexp: "^#?PAPERLESS_DBHOST=(.*)$" line: '#PAPERLESS_DBHOST=\1' backrefs: yes when: paperlessng_db_type == 'sqlite' - name: configure paperless-ng database [postgresql] lineinfile: path: "{{ paperlessng_directory }}/paperless.conf.template" regexp: "^#?{{ item.regexp }}=" line: "{{ item.line }}" with_items: - regexp: PAPERLESS_DBHOST line: "PAPERLESS_DBHOST={{ paperlessng_db_host }}" - regexp: PAPERLESS_DBPORT line: "PAPERLESS_DBPORT={{ paperlessng_db_port }}" - regexp: PAPERLESS_DBNAME line: "PAPERLESS_DBNAME={{ paperlessng_db_name }}" - regexp: PAPERLESS_DBUSER line: "PAPERLESS_DBUSER={{ paperlessng_db_user }}" - regexp: PAPERLESS_DBPASS line: "PAPERLESS_DBPASS={{ paperlessng_db_pass }}" - regexp: PAPERLESS_DBSSLMODE line: "PAPERLESS_DBSSLMODE={{ paperlessng_db_sslmode }}" when: paperlessng_db_type == 'postgresql' no_log: yes - name: deploy paperless-ng configuration copy: src: "{{ paperlessng_directory }}/paperless.conf.template" remote_src: yes dest: /etc/paperless.conf owner: root group: root mode: "0644" register: configuration - name: create paperlessng venv become: yes become_user: "{{ paperlessng_system_user }}" command: cmd: "python3 -m virtualenv {{ paperlessng_virtualenv }} -p /usr/bin/python3" creates: "{{ paperlessng_virtualenv }}" register: venv - block: - name: install paperlessng requirements become: yes become_user: "{{ paperlessng_system_user }}" pip: requirements: "{{ paperlessng_directory }}/requirements.txt" executable: "{{ paperlessng_virtualenv }}/bin/pip3" extra_args: --upgrade - name: migrate database schema become: yes become_user: "{{ paperlessng_system_user }}" command: "{{ paperlessng_virtualenv }}/bin/python3 {{ paperlessng_directory }}/src/manage.py migrate" register: database_schema changed_when: '"No migrations to apply." not in database_schema.stdout' when: not reconfigure_only - name: configure paperless superuser become: yes become_user: "{{ paperlessng_system_user }}" # "manage.py createsuperuser" only works on interactive TTYs vars: creation_script: | from django.contrib.auth.models import User from django.contrib.auth.hashers import get_hasher if User.objects.filter(username='{{ paperlessng_superuser_name }}').exists(): user = User.objects.get(username='{{ paperlessng_superuser_name }}') old = user.__dict__.copy() user.is_superuser = True user.email = '{{ paperlessng_superuser_email }}' user.set_password('{{ paperlessng_superuser_password }}') user.save() new = user.__dict__ algorithm, iterations, old_salt, old_hash = old['password'].split('$') new_password_old_salt = get_hasher(algorithm).encode(password='{{ paperlessng_superuser_password }}', salt=old_salt, iterations=int(iterations)) _, _, _, new_hash = new_password_old_salt.split('$') if not (old_hash == new_hash and old['is_superuser'] == new['is_superuser'] and old['email'] == new['email']): print('changed') else: User.objects.create_superuser('{{ paperlessng_superuser_name }}', '{{ paperlessng_superuser_email }}', '{{ paperlessng_superuser_password }}') print('changed') command: '{{ paperlessng_virtualenv }}/bin/python3 {{ paperlessng_directory }}/src/manage.py shell -c "{{ creation_script }}"' register: superuser changed_when: superuser.stdout == 'changed' no_log: yes - name: set ownership and permissions on paperlessng venv file: path: "{{ paperlessng_virtualenv }}" state: directory recurse: yes owner: "{{ paperlessng_system_user }}" group: "{{ paperlessng_system_group }}" mode: g-w,o-rwx when: venv.changed or not reconfigure_only - name: configure ghostscript for PDF lineinfile: path: /etc/ImageMagick-6/policy.xml regexp: '(\s+)' line: '\1' backrefs: yes - name: configure gunicorn web server lineinfile: path: "{{ paperlessng_directory }}/gunicorn.conf.py" regexp: '^bind = ' line: "bind = '{{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}'" - name: configure systemd services ini_file: path: "{{ paperlessng_directory }}/scripts/{{ item[0] }}" section: "Service" option: "{{ item[1].option }}" value: "{{ item[1].value }}" with_nested: - [ paperless-consumer.service, paperless-scheduler.service, paperless-webserver.service, ] - [ # https://www.freedesktop.org/software/systemd/man/systemd.exec.html { option: "User", value: "{{ paperlessng_system_user }}" }, { option: "Group", value: "{{ paperlessng_system_group }}" }, { option: "WorkingDirectory", value: "{{ paperlessng_directory }}/src" }, { option: "ProtectSystem", value: "full" }, { option: "NoNewPrivileges", value: "true" }, { option: "PrivateUsers", value: "true" }, { option: "PrivateDevices", value: "true" }, ] - name: configure paperless-consumer service ini_file: path: "{{ paperlessng_directory }}/scripts/paperless-consumer.service" section: "Service" option: "ExecStart" value: "{{ paperlessng_virtualenv }}/bin/python3 manage.py document_consumer" - name: configure paperless-scheduler service ini_file: path: "{{ paperlessng_directory }}/scripts/paperless-scheduler.service" section: "Service" option: "ExecStart" value: "{{ paperlessng_virtualenv }}/bin/python3 manage.py qcluster" - name: configure paperless-webserver service ini_file: path: "{{ paperlessng_directory }}/scripts/paperless-webserver.service" section: "Service" option: "ExecStart" value: "{{ paperlessng_virtualenv }}/bin/gunicorn -c {{ paperlessng_directory }}/gunicorn.conf.py paperless.asgi:application" - name: copy systemd services copy: src: "{{ paperlessng_directory }}/scripts/{{ item }}" remote_src: yes dest: "/etc/systemd/system/{{ item }}" with_items: - paperless-consumer.service - paperless-scheduler.service - paperless-webserver.service register: paperless_services - name: reload systemd daemon systemd: name: "{{ item }}" state: restarted daemon_reload: yes with_items: - paperless-consumer - paperless-scheduler - paperless-webserver when: paperless_services.changed or configuration.changed - name: enable paperlessng services systemd: name: "{{ item }}" enabled: yes masked: no state: started with_items: - paperless-consumer - paperless-scheduler - paperless-webserver