From e293e29bbb76654916b1558e208be1e7cfd1b8cd Mon Sep 17 00:00:00 2001 From: Daniel Berteaud Date: Wed, 26 Jan 2022 18:00:05 +0100 Subject: [PATCH] Update to 2022-01-26 18:00 --- roles/taiga/defaults/main.yml | 76 ++++++ roles/taiga/files/taiga.te | 22 ++ roles/taiga/handlers/main.yml | 9 + roles/taiga/meta/main.yml | 9 + roles/taiga/tasks/archive_post.yml | 10 + roles/taiga/tasks/archive_pre.yml | 38 +++ roles/taiga/tasks/cleanup.yml | 15 ++ roles/taiga/tasks/conf.yml | 88 +++++++ roles/taiga/tasks/directories.yml | 34 +++ roles/taiga/tasks/facts.yml | 65 ++++++ roles/taiga/tasks/install.yml | 135 +++++++++++ roles/taiga/tasks/main.yml | 14 ++ roles/taiga/tasks/services.yml | 10 + roles/taiga/tasks/user.yml | 9 + roles/taiga/tasks/write_version.yml | 5 + roles/taiga/templates/back/config.py.j2 | 220 ++++++++++++++++++ roles/taiga/templates/events/env.j2 | 4 + roles/taiga/templates/front/conf.json.j2 | 29 +++ roles/taiga/templates/nginx.conf.j2 | 76 ++++++ roles/taiga/templates/post-backup.j2 | 5 + roles/taiga/templates/pre-backup.j2 | 13 ++ roles/taiga/templates/protected/env.j2 | 3 + roles/taiga/templates/taiga-async.service.j2 | 25 ++ roles/taiga/templates/taiga-back.service.j2 | 24 ++ roles/taiga/templates/taiga-events.service.j2 | 23 ++ .../templates/taiga-protected.service.j2 | 23 ++ roles/taiga/vars/RedHat-8.yml | 31 +++ 27 files changed, 1015 insertions(+) create mode 100644 roles/taiga/defaults/main.yml create mode 100644 roles/taiga/files/taiga.te create mode 100644 roles/taiga/handlers/main.yml create mode 100644 roles/taiga/meta/main.yml create mode 100644 roles/taiga/tasks/archive_post.yml create mode 100644 roles/taiga/tasks/archive_pre.yml create mode 100644 roles/taiga/tasks/cleanup.yml create mode 100644 roles/taiga/tasks/conf.yml create mode 100644 roles/taiga/tasks/directories.yml create mode 100644 roles/taiga/tasks/facts.yml create mode 100644 roles/taiga/tasks/install.yml create mode 100644 roles/taiga/tasks/main.yml create mode 100644 roles/taiga/tasks/services.yml create mode 100644 roles/taiga/tasks/user.yml create mode 100644 roles/taiga/tasks/write_version.yml create mode 100644 roles/taiga/templates/back/config.py.j2 create mode 100644 roles/taiga/templates/events/env.j2 create mode 100644 roles/taiga/templates/front/conf.json.j2 create mode 100644 roles/taiga/templates/nginx.conf.j2 create mode 100644 roles/taiga/templates/post-backup.j2 create mode 100644 roles/taiga/templates/pre-backup.j2 create mode 100644 roles/taiga/templates/protected/env.j2 create mode 100644 roles/taiga/templates/taiga-async.service.j2 create mode 100644 roles/taiga/templates/taiga-back.service.j2 create mode 100644 roles/taiga/templates/taiga-events.service.j2 create mode 100644 roles/taiga/templates/taiga-protected.service.j2 create mode 100644 roles/taiga/vars/RedHat-8.yml diff --git a/roles/taiga/defaults/main.yml b/roles/taiga/defaults/main.yml new file mode 100644 index 0000000..d048451 --- /dev/null +++ b/roles/taiga/defaults/main.yml @@ -0,0 +1,76 @@ +--- + +# Version to deploy +taiga_version: 6.5.0 +# Where taiga will be installed +taiga_root_dir: /opt/taiga +# User under which taiga will run (will be created) +taiga_user: taiga + +# Archive URL and expected checksum +taiga_archives: + back: + url: https://github.com/kaleidos-ventures/taiga-back/archive/refs/tags/{{ taiga_version }}.tar.gz + sha256: aa68e689a0095a1726869291eb8bcfe82e828d51188ed4a9eb797ed9a664aa0c + front: + url: https://github.com/kaleidos-ventures/taiga-front-dist/archive/refs/tags/{{ taiga_version }}.tar.gz + sha256: 1d2595b32346eeef1796db85d1c5701258e0c038be2bab9e86c0c7d6f99b034b + dir: taiga-front-dist-{{ taiga_version }} + events: + url: https://github.com/kaleidos-ventures/taiga-events/archive/refs/tags/{{ taiga_version }}.tar.gz + sha256: c6307e1246b97a2aa7f53755dbceb98b24256badf8282b37b8a24b51d08b583f + protected: + url: https://github.com/kaleidos-ventures/taiga-protected/archive/refs/tags/{{ taiga_version }}.tar.gz + sha256: 8465eaf078dee7b61f4fd5dbb216db94ea455b3c8d69c19c10ae33ff481e46f0 + +# Database settings +taiga_db_server: "{{ pg_server | default('localhost') }}" +taiga_db_port: 5432 +taiga_db_name: taiga +taiga_db_user: taiga +# If the password is not defined, a random one will be generated and stored under {{ taiga_root_dir }}/meta/ansible_dbpass +# taiga_db_pass: S3cr3t. + +# Secret key used by various components +# If not defined, a random one will be created and stored under {{ taiga_root_dir }}/meta/ansible_secret_key +# taiga_secret_key: S3cr3t. + +# AMQP settings +taiga_amqp_server: localhost +taiga_amqp_port: 5672 +taiga_amqp_user: taiga +taiga_amqp_vhost: taiga +# If the password is not defined, a random one will be create and stored under {{ taiga_root_dir }}/meta/ansible_amqp_pass +# But this is only useful when taiga_amqp_server is localhost. If you use a remote AMQP server, you'll have to define the pass +# taiga_amqp_pass: S3cr3t. + +# Ports used by taiga components +# they will only be accessible to localhost and exposed by nginx +# If you want to restrict access at the firewall level (for example, to only allow a reverse proxy), you need to +# set nginx_src_ip to the list of IP (or CIDR network) which will have access to port 80/443 +taiga_ports: + back: 8001 + protected: 8003 + events: 8888 + +# Public URL to reach taiga +taiga_public_url: https://{{ inventory_hostname }}/ + +# A default admin user is created during installation +# You can set its password, or a random one will be generated and stored under {{ taiga_root_dir }}/meta/ansible_admin_pass +# taiga_admin_pass: S3cr3t. + +# Registration enabled ? +taiga_user_registration: False +# Max upload file size (in MB) +taiga_max_upload_file_size: 20 + +# Email settings +taiga_email_from: taiga-no-reply@{{ ansible_domain }} +taiga_smtp_server: localhost +taiga_smtp_port: 25 +taiga_smtp_tls: "{{ (taiga_smtp_port == 587) | ternary(True, False) }}" +taiga_smtp_ssl: "{{ (taiga_smtp_port == 465) | ternary(True, False) }}" +# If your SMTP server requires an authentication, set the following variables +#taiga_smtp_user: taiga@example.org +#taiga_smtp_pass: p@ssw0rd diff --git a/roles/taiga/files/taiga.te b/roles/taiga/files/taiga.te new file mode 100644 index 0000000..1f0b575 --- /dev/null +++ b/roles/taiga/files/taiga.te @@ -0,0 +1,22 @@ +module taiga 1.2; + +require { + type ldconfig_exec_t; + type etc_t; + type init_t; + type init_tmp_t; + type ldconfig_t; + type amqp_port_t; + type postgresql_port_t; + type usr_t; + class file { append execute_no_trans write map }; + class tcp_socket name_connect; + class process2 nnp_transition; +} + +allow init_t ldconfig_exec_t:file execute_no_trans; +allow init_t ldconfig_t:process2 nnp_transition; +allow init_t usr_t:file { write append }; +allow init_t init_tmp_t:file map; +allow init_t postgresql_port_t:tcp_socket name_connect; +allow init_t amqp_port_t:tcp_socket name_connect; diff --git a/roles/taiga/handlers/main.yml b/roles/taiga/handlers/main.yml new file mode 100644 index 0000000..b8113c2 --- /dev/null +++ b/roles/taiga/handlers/main.yml @@ -0,0 +1,9 @@ +--- + +- name: restart taiga + service: name=taiga-{{ item }} state=restarted + loop: + - back + - async + - events + - protected diff --git a/roles/taiga/meta/main.yml b/roles/taiga/meta/main.yml new file mode 100644 index 0000000..b4f5f9f --- /dev/null +++ b/roles/taiga/meta/main.yml @@ -0,0 +1,9 @@ +--- + +dependencies: + - role: nodejs + - role: rabbitmq_server + when: taiga_amqp_server in ['localhost', '127.0.0.1'] + - role: nginx + - role: postgresql_server + when: taiga_db_server in ['localhost', '127.0.0.1'] diff --git a/roles/taiga/tasks/archive_post.yml b/roles/taiga/tasks/archive_post.yml new file mode 100644 index 0000000..05ba2cf --- /dev/null +++ b/roles/taiga/tasks/archive_post.yml @@ -0,0 +1,10 @@ +--- + +- name: Compress previous version + command: tar cf {{ taiga_root_dir }}/archives/{{ taiga_current_version }}.tar.zst --use-compress-program=zstd ./ + args: + chdir: "{{ taiga_root_dir }}/archives/{{ taiga_current_version }}" + warn: False + environment: + ZSTD_CLEVEL: 10 + tags: taiga diff --git a/roles/taiga/tasks/archive_pre.yml b/roles/taiga/tasks/archive_pre.yml new file mode 100644 index 0000000..54f4eab --- /dev/null +++ b/roles/taiga/tasks/archive_pre.yml @@ -0,0 +1,38 @@ +--- + +- name: Create the archive dir + file: path={{ taiga_root_dir }}/archives/{{ taiga_current_version }} state=directory + tags: taiga + +- name: Install postgresql client + package: + name: + - postgresql14 + tags: taiga + +- name: Archive previous version + synchronize: + src: "{{ taiga_root_dir }}/{{ item }}" + dest: "{{ taiga_root_dir }}/archives/{{ taiga_current_version }}/" + recursive: True + delete: True + compress: False + loop: + - venv + - app + delegate_to: "{{ inventory_hostname }}" + tags: taiga + +- name: Dump the database + command: > + /usr/pgsql-14/bin/pg_dump + --clean + --create + --host={{ taiga_db_server | quote }} + --port={{ taiga_db_port | quote }} + --username={{ taiga_db_user | quote }} {{ taiga_db_name | quote }} + --file="{{ taiga_root_dir }}/archives/{{ taiga_current_version }}/{{ taiga_db_name }}.sql" + environment: + - PGPASSWORD: "{{ taiga_db_pass }}" + tags: taiga + diff --git a/roles/taiga/tasks/cleanup.yml b/roles/taiga/tasks/cleanup.yml new file mode 100644 index 0000000..761e495 --- /dev/null +++ b/roles/taiga/tasks/cleanup.yml @@ -0,0 +1,15 @@ +--- + +- name: Remove tmp and obsolete files + file: path={{ item }} state=absent + loop: + - "{{ taiga_root_dir }}/tmp/taiga-back-{{ taiga_version }}.tar.gz" + - "{{ taiga_root_dir }}/tmp/taiga-back-{{ taiga_version }}" + - "{{ taiga_root_dir }}/tmp/taiga-front-dist-{{ taiga_version }}.tar.gz" + - "{{ taiga_root_dir }}/tmp/taiga-front-dist-{{ taiga_version }}" + - "{{ taiga_root_dir }}/tmp/taiga-events-{{ taiga_version }}.tar.gz" + - "{{ taiga_root_dir }}/tmp/taiga-events-{{ taiga_version }}" + - "{{ taiga_root_dir }}/tmp/taiga-protected-{{ taiga_version }}.tar.gz" + - "{{ taiga_root_dir }}/tmp/taiga-protected-{{ taiga_version }}" + - "{{ taiga_root_dir }}/archives/{{ taiga_current_version }}" + tags: taiga diff --git a/roles/taiga/tasks/conf.yml b/roles/taiga/tasks/conf.yml new file mode 100644 index 0000000..7954e4f --- /dev/null +++ b/roles/taiga/tasks/conf.yml @@ -0,0 +1,88 @@ +--- + +- name: Deploy configuration + template: src={{ item.src }} dest={{ item.dest }} group={{ item.group | default(omit) }} mode={{ item.mode | default(omit) }} + loop: + - src: back/config.py.j2 + dest: "{{ taiga_root_dir }}/app/back/settings/config.py" + group: "{{ taiga_user }}" + mode: 640 + - src: front/conf.json.j2 + dest: "{{ taiga_root_dir }}/app/front/dist/conf.json" + - src: events/env.j2 + dest: "{{ taiga_root_dir }}/app/events/.env" + group: "{{ taiga_user }}" + mode: 640 + - src: protected/env.j2 + dest: "{{ taiga_root_dir }}/app/protected/.env" + group: "{{ taiga_user }}" + mode: 640 + notify: restart taiga + tags: taiga + +- name: Deploy nginx configuration + template: src=nginx.conf.j2 dest=/etc/nginx/ansible_conf.d/10-taiga.conf + notify: reload nginx + tags: taiga + +- name: Create RabbitMQ user and vhost + shell: | + {% if taiga_amqp_user_exists.rc == 0 %} + rabbitmqctl change_password {{ taiga_amqp_user }} {{ taiga_amqp_pass }} + {% else %} + rabbitmqctl add_user {{ taiga_amqp_user }} {{ taiga_amqp_pass }} + {% endif %} + rabbitmqctl add_vhost {{ taiga_amqp_vhost }} + rabbitmqctl set_permissions -p {{ taiga_amqp_vhost }} {{ taiga_amqp_user }} ".*" ".*" ".*" + when: taiga_amqp_server in ['localhost', '127.0.0.1'] + tags: taiga + +- when: taiga_install_mode != 'none' + block: + + - name: Migrate database + django_manage: + command: migrate + app_path: "{{ taiga_root_dir }}/app/back" + virtualenv: "{{ taiga_root_dir }}/venv" + become_user: "{{ taiga_user }}" + + - name: Compile messages + django_manage: + command: compilemessages + app_path: "{{ taiga_root_dir }}/app/back" + virtualenv: "{{ taiga_root_dir }}/venv" + + - name: Collect static files + django_manage: + command: collectstatic + app_path: "{{ taiga_root_dir }}/app/back" + virtualenv: "{{ taiga_root_dir }}/venv" + + environment: + DJANGO_SETTINGS_MODULE: settings.config + CELERY_ENABLED: False + tags: taiga + +- when: taiga_install_mode == 'install' + block: + + - name: Create admin user + django_manage: + command: createsuperuser --noinput --username admin --email admin@{{ ansible_domain }} + app_path: "{{ taiga_root_dir }}/app/back" + virtualenv: "{{ taiga_root_dir }}/venv" + + - name: load initial data + django_manage: + command: loaddata initial_project_templates + app_path: "{{ taiga_root_dir }}/app/back" + virtualenv: "{{ taiga_root_dir }}/venv" + + environment: + DJANGO_SUPERUSER_PASSWORD: '{{ taiga_admin_pass }}' + DJANGO_SETTINGS_MODULE: settings.config + CELERY_ENABLED: False + become_user: "{{ taiga_user }}" + when: taiga_install_mode == 'install' + tags: taiga diff --git a/roles/taiga/tasks/directories.yml b/roles/taiga/tasks/directories.yml new file mode 100644 index 0000000..157e9ff --- /dev/null +++ b/roles/taiga/tasks/directories.yml @@ -0,0 +1,34 @@ +--- + +- name: Create needed directories + file: path={{ item.dir }} state=directory owner={{ item.owner | default(omit) }} group={{ item.group | default(omit) }} mode={{ item.mode | default(omit) }} + loop: + - dir: "{{ taiga_root_dir }}" + owner: "{{ taiga_user }}" + group: nginx + mode: 750 + - dir: "{{ taiga_root_dir }}/backup" + owner: root + group: root + mode: 700 + - dir: "{{ taiga_root_dir }}/meta" + owner: root + group: root + mode: 700 + - dir: "{{ taiga_root_dir }}/archives" + owner: root + group: root + mode: 700 + - dir: "{{ taiga_root_dir }}/tmp" + owner: "{{ taiga_user }}" + mode: 700 + - dir: "{{ taiga_root_dir }}/app" + - dir: "{{ taiga_root_dir }}/app/back" + - dir: "{{ taiga_root_dir }}/app/front" + - dir: "{{ taiga_root_dir }}/app/events" + - dir: "{{ taiga_root_dir }}/app/protected" + - dir: "{{ taiga_root_dir }}/data/media" + owner: "{{ taiga_user }}" + group: "{{ taiga_user }}" + mode: 750 + tags: taiga diff --git a/roles/taiga/tasks/facts.yml b/roles/taiga/tasks/facts.yml new file mode 100644 index 0000000..9475372 --- /dev/null +++ b/roles/taiga/tasks/facts.yml @@ -0,0 +1,65 @@ +--- + +# Load distribution specific variables +- include_vars: "{{ item }}" + with_first_found: + - "{{ role_path }}/vars/{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml" + - "{{ role_path }}/vars/{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml" + - "{{ role_path }}/vars/{{ ansible_distribution }}.yml" + - "{{ role_path }}/vars/{{ ansible_os_family }}.yml" + tags: taiga + +# Detect installed version (if any) +- block: + - import_tasks: ../includes/webapps_set_install_mode.yml + vars: + - root_dir: "{{ taiga_root_dir }}" + - version: "{{ taiga_version }}" + - set_fact: taiga_install_mode={{ (install_mode == 'upgrade' and not taiga_manage_upgrade) | ternary('none',install_mode) }} + - set_fact: taiga_current_version={{ current_version | default('') }} + tags: taiga + +# Generate a password for the database if needed +- when: taiga_db_pass is not defined + block: + - import_tasks: ../includes/get_rand_pass.yml + vars: + - pass_file: "{{ taiga_root_dir }}/meta/ansible_dbpass" + - set_fact: taiga_db_pass={{ rand_pass }} + tags: taiga + +# Create a random secret key +- when: taiga_secret_key is not defined + block: + - import_tasks: ../includes/get_rand_pass.yml + vars: + - pass_file: "{{ taiga_root_dir }}/meta/ansible_secret_key" + - set_fact: taiga_secret_key={{ rand_pass }} + tags: taiga + +# AMQP password +- when: taiga_amqp_pass is not defined + block: + - import_tasks: ../includes/get_rand_pass.yml + vars: + - pass_file: "{{ taiga_root_dir }}/meta/ansible_amqp_pass" + - complex: False + - set_fact: taiga_amqp_pass={{ rand_pass }} + tags: taiga + +# Default admin pass +- when: taiga_admin_pass is not defined + block: + - import_tasks: ../includes/get_rand_pass.yml + vars: + - pass_file: "{{ taiga_root_dir }}/meta/ansible_admin_pass" + - complex: False + - set_fact: taiga_admin_pass={{ rand_pass }} + tags: taiga + +- name: Check if RabbitMQ user exists + shell: rabbitmqctl list_users | grep -qP '^{{ taiga_amqp_user }}\s+' + register: taiga_amqp_user_exists + failed_when: False + changed_when: False + tags: taiga diff --git a/roles/taiga/tasks/install.yml b/roles/taiga/tasks/install.yml new file mode 100644 index 0000000..39c7012 --- /dev/null +++ b/roles/taiga/tasks/install.yml @@ -0,0 +1,135 @@ +--- + +- name: Install packages + package: name={{ taiga_packages }} + tags: taiga + +- name: Stop services during upgrade + service: name={{ item }} state=stopped + loop: + - taiga-back + - taiga-async + - taiga-events + - taiga-protected + when: taiga_install_mode == 'upgrade' + tags: taiga + +- when: taiga_install_mode != 'none' + block: + - name: Download components + get_url: + url: "{{ taiga_archives[item].url }}" + dest: "{{ taiga_root_dir }}/tmp" + checksum: sha256:{{ taiga_archives[item].sha256 }} + loop: "{{ taiga_archives.keys() | list }}" + + - name: Extract archives + unarchive: + src: "{{ taiga_root_dir }}/tmp/{{ taiga_archives[item].dir | default('taiga-' ~ item ~ '-' ~ taiga_version) }}.tar.gz" + dest: "{{ taiga_root_dir }}/tmp" + remote_src: True + loop: "{{ taiga_archives.keys() | list }}" + + - name: Move components to their final dir + synchronize: + src: "{{ taiga_root_dir }}/tmp/{{ taiga_archives[item].dir | default('taiga-' ~ item ~ '-' ~ taiga_version) }}/" + dest: "{{ taiga_root_dir }}/app/{{ item }}/" + delete: True + compress: False + loop: "{{ taiga_archives.keys() | list }}" + delegate_to: "{{ inventory_hostname }}" + + - name: Create the virtualenv + pip: + name: + - pip + - wheel + virtualenv: "{{ taiga_root_dir }}/venv" + virtualenv_command: /bin/python3.9 -m venv + + - name: Install taiga-back dependencies + pip: + requirements: "{{ taiga_root_dir }}/app/back/requirements.txt" + state: "{{ (taiga_install_mode == 'upgrade') | ternary('latest', 'present') }}" + virtualenv: "{{ taiga_root_dir }}/venv" + virtualenv_command: /bin/python3.9 -m venv + #environment: + # PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/pgsql-14/bin/ + + - name: Install the contrib-protected plugin + pip: + name: git+https://github.com/kaleidos-ventures/taiga-contrib-protected.git@stable#egg=taiga-contrib-protected + virtualenv: "{{ taiga_root_dir }}/venv" + virtualenv_command: /bin/python3.9 -m venv + + - name: Install dependencies for taiga-events + npm: + path: "{{ taiga_root_dir }}/app/events/" + + - name: Install requirements for taiga-protected + pip: + requirements: "{{ taiga_root_dir }}/app/protected/requirements.txt" + state: "{{ (taiga_install_mode == 'upgrade') | ternary('latest', 'present') }}" + virtualenv: "{{ taiga_root_dir }}/venv" + virtualenv_command: /bin/python3.9 -m venv + + tags: taiga + +- block: + - name: Create the PostgreSQL role + postgresql_user: + db: postgres + name: "{{ taiga_db_user }}" + password: "{{ taiga_db_pass }}" + login_host: "{{ taiga_db_server }}" + login_user: sqladmin + login_password: "{{ pg_admin_pass }}" + + - name: Create the PostgreSQL database + postgresql_db: + name: "{{ taiga_db_name }}" + encoding: UTF-8 + template: template0 + owner: "{{ taiga_db_user }}" + login_host: "{{ taiga_db_server }}" + login_user: sqladmin + login_password: "{{ pg_admin_pass }}" + + tags: taiga + +- name: Install service units + template: src={{ item }}.j2 dest=/etc/systemd/system/{{ item }} + loop: + - taiga-back.service + - taiga-async.service + - taiga-events.service + - taiga-protected.service + register: taiga_units + tags: taiga + +- name: Reload systemd + systemd: daemon_reload=True + when: taiga_units.results | selectattr('changed','equalto',True) | list | length > 0 + tags: taiga + +- name: Install backup hooks + template: src={{ item }}-backup.j2 dest=/etc/backup/{{ item }}.d/taiga mode=700 + loop: + - pre + - post + tags: taiga + +- name: Copy SELinux policy + copy: src=taiga.te dest=/etc/selinux/targeted/local/ + register: taiga_selinux_policy + tags: taiga + +- name: Compile and load SELinux policy + shell: | + cd /etc/selinux/targeted/local/ + checkmodule -M -m -o taiga.mod taiga.te + semodule_package -o taiga.pp -m taiga.mod + semodule -i /etc/selinux/targeted/local/taiga.pp + when: ansible_selinux.status == 'enabled' and taiga_selinux_policy.changed + tags: taiga + diff --git a/roles/taiga/tasks/main.yml b/roles/taiga/tasks/main.yml new file mode 100644 index 0000000..08890e4 --- /dev/null +++ b/roles/taiga/tasks/main.yml @@ -0,0 +1,14 @@ +--- + +- include: user.yml +- include: directories.yml +- include: facts.yml +- include: archive_pre.yml + when: taiga_install_mode == 'upgrade' +- include: install.yml +- include: conf.yml +- include: services.yml +- include: write_version.yml +- include: archive_post.yml + when: taiga_install_mode == 'upgrade' +- include: cleanup.yml diff --git a/roles/taiga/tasks/services.yml b/roles/taiga/tasks/services.yml new file mode 100644 index 0000000..5f05315 --- /dev/null +++ b/roles/taiga/tasks/services.yml @@ -0,0 +1,10 @@ +--- + +- name: Start and enable services + service: name={{ item }} state=started enabled=True + loop: + - taiga-back + - taiga-async + - taiga-events + - taiga-protected + tags: taiga diff --git a/roles/taiga/tasks/user.yml b/roles/taiga/tasks/user.yml new file mode 100644 index 0000000..7aae7b0 --- /dev/null +++ b/roles/taiga/tasks/user.yml @@ -0,0 +1,9 @@ +--- + +- name: Create user + user: + name: "{{ taiga_user }}" + home: "{{ taiga_root_dir }}" + system: True + shell: /sbin/nologin + tags: taiga diff --git a/roles/taiga/tasks/write_version.yml b/roles/taiga/tasks/write_version.yml new file mode 100644 index 0000000..67cefe5 --- /dev/null +++ b/roles/taiga/tasks/write_version.yml @@ -0,0 +1,5 @@ +--- + +- name: Write installed version + copy: content={{ taiga_version }} dest={{ taiga_root_dir }}/meta/ansible_version + tags: taiga diff --git a/roles/taiga/templates/back/config.py.j2 b/roles/taiga/templates/back/config.py.j2 new file mode 100644 index 0000000..78fc7f5 --- /dev/null +++ b/roles/taiga/templates/back/config.py.j2 @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +import os + +from .common import * + +######################################### +## GENERIC +######################################### + +DEBUG = False + +#ADMINS = ( +# ("Admin", "example@example.com"), +#) + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': '{{ taiga_db_name }}', + 'USER': '{{ taiga_db_user }}', + 'PASSWORD': '{{ taiga_db_pass }}', + 'HOST': '{{ taiga_db_server }}', + 'PORT': '{{ taiga_db_port }}', + } +} + +SECRET_KEY = "{{ taiga_secret_key }}" + +TAIGA_SITES_SCHEME = "{{ taiga_public_url | urlsplit('scheme') }}" +TAIGA_SITES_DOMAIN = "{{ taiga_public_url | urlsplit('hostname') }}" +FORCE_SCRIPT_NAME = "{{ (taiga_public_url | urlsplit('path') == '/') | ternary('', taiga_public_url | urlsplit('path')) }}" + +TAIGA_URL = f"{ TAIGA_SITES_SCHEME }://{ TAIGA_SITES_DOMAIN }{ FORCE_SCRIPT_NAME }" +SITES = { + "api": { "name": "api", "scheme": TAIGA_SITES_SCHEME, "domain": TAIGA_SITES_DOMAIN }, + "front": { "name": "front", "scheme": TAIGA_SITES_SCHEME, "domain": f"{ TAIGA_SITES_DOMAIN }{ FORCE_SCRIPT_NAME }" } +} + +# Setting DEFAULT_PROJECT_SLUG_PREFIX to false +# removes the username from project slug +DEFAULT_PROJECT_SLUG_PREFIX = False + +######################################### +## MEDIA AND STATIC +######################################### + +MEDIA_ROOT = '{{ taiga_root_dir }}/data/media/' +MEDIA_URL = f"{ TAIGA_URL }/media/" +DEFAULT_FILE_STORAGE = "taiga_contrib_protected.storage.ProtectedFileSystemStorage" +THUMBNAIL_DEFAULT_STORAGE = DEFAULT_FILE_STORAGE + +# STATIC_ROOT = '/home/taiga/static' +STATIC_URL = f"{ TAIGA_URL }/static/" + +######################################### +## EMAIL +######################################### +# https://docs.djangoproject.com/en/3.1/topics/email/ +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +CHANGE_NOTIFICATIONS_MIN_INTERVAL = 120 # seconds + +DEFAULT_FROM_EMAIL = '{{ taiga_email_from }}' +EMAIL_USE_TLS = {{ taiga_smtp_tls | ternary('True', 'False') }} +EMAIL_USE_SSL = {{ taiga_smtp_ssl | ternary('True', 'False') }} +EMAIL_HOST = '{{ taiga_smtp_server }}' +EMAIL_PORT = {{ taiga_smtp_port }} +{% if taiga_smtp_user is defined and taiga_smtp_pass is defined %} +EMAIL_HOST_USER = '{{ taiga_smtp_user }}' +EMAIL_HOST_PASSWORD = '{{ taiga_smtp_pass }}' +{% endif %} + +######################################### +## EVENTS +######################################### +EVENTS_PUSH_BACKEND = "taiga.events.backends.rabbitmq.EventsPushBackend" +EVENTS_PUSH_BACKEND_OPTIONS = { + "url": "amqp://{{ taiga_amqp_user }}:{{ taiga_amqp_pass }}@{{ taiga_amqp_server }}:{{ taiga_amqp_port }}/{{ taiga_amqp_vhost }}" +} + + +######################################### +## TAIGA ASYNC +######################################### +CELERY_ENABLED = os.getenv('CELERY_ENABLED', 'True') == 'True' + +from kombu import Queue # noqa + +CELERY_BROKER_URL = "amqp://{{ taiga_amqp_user }}:{{ taiga_amqp_pass }}@{{ taiga_amqp_server }}:{{ taiga_amqp_port }}/{{ taiga_amqp_vhost }}" +CELERY_RESULT_BACKEND = None # for a general installation, we don't need to store the results +CELERY_ACCEPT_CONTENT = ['pickle', ] # Values are 'pickle', 'json', 'msgpack' and 'yaml' +CELERY_TASK_SERIALIZER = "pickle" +CELERY_RESULT_SERIALIZER = "pickle" +CELERY_TIMEZONE = "{{ system_tz | default('Europe/Paris') }}" +CELERY_TASK_DEFAULT_QUEUE = 'tasks' +CELERY_QUEUES = ( + Queue('tasks', routing_key='task.#'), + Queue('transient', routing_key='transient.#', delivery_mode=1) +) +CELERY_TASK_DEFAULT_EXCHANGE = 'tasks' +CELERY_TASK_DEFAULT_EXCHANGE_TYPE = 'topic' +CELERY_TASK_DEFAULT_ROUTING_KEY = 'task.default' + + +######################################### +## CONTRIBS +######################################### +# INSTALLED_APPS += [ +# "taiga_contrib_slack", +# "taiga_contrib_github_auth", +# "taiga_contrib_gitlab_auth" +# ] +# +# GITHUB_API_CLIENT_ID = "changeme" +# GITHUB_API_CLIENT_SECRET = "changeme" +# +# GITLAB_API_CLIENT_ID = "changeme" +# GITLAB_API_CLIENT_SECRET = "changeme" +# GITLAB_URL = "changeme" + + +######################################### +## TELEMETRY +######################################### + +ENABLE_TELEMETRY = False + +######################################### +## REGISTRATION +######################################### + +PUBLIC_REGISTER_ENABLED = False + +######################################### +## THROTTLING +######################################### + +#REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = { +# "anon-write": "20/min", +# "user-write": None, +# "anon-read": None, +# "user-read": None, +# "import-mode": None, +# "import-dump-mode": "1/minute", +# "create-memberships": None, +# "login-fail": None, +# "register-success": None, +# "user-detail": None, +# "user-update": None, +#} + +# This list should contain: +# - Taiga users IDs +# - Valid clients IP addresses (X-Forwarded-For header) +#REST_FRAMEWORK["DEFAULT_THROTTLE_WHITELIST"] = [] + +# LIMIT ALLOWED DOMAINS FOR REGISTER AND INVITE +# None or [] values in USER_EMAIL_ALLOWED_DOMAINS means allow any domain +#USER_EMAIL_ALLOWED_DOMAINS = None + +# PUCLIC OR PRIVATE NUMBER OF PROJECT PER USER +#MAX_PRIVATE_PROJECTS_PER_USER = None # None == no limit +#MAX_PUBLIC_PROJECTS_PER_USER = None # None == no limit +#MAX_MEMBERSHIPS_PRIVATE_PROJECTS = None # None == no limit +#MAX_MEMBERSHIPS_PUBLIC_PROJECTS = None # None == no limit + + +######################################### +## SITEMAP +######################################### + +# If is True /front/sitemap.xml show a valid sitemap of taiga-front client +#FRONT_SITEMAP_ENABLED = False +#FRONT_SITEMAP_CACHE_TIMEOUT = 24*60*60 # In second + + +######################################### +## FEEDBACK +######################################### + +# Note: See config in taiga-front too +FEEDBACK_ENABLED = False +#FEEDBACK_EMAIL = "support@taiga.io" + + +######################################### +## STATS +######################################### + +#STATS_ENABLED = False +#STATS_CACHE_TIMEOUT = 60*60 # In second + + +######################################### +## IMPORTERS +######################################### + +# Configuration for the GitHub importer +# Remember to enable it in the front client too. +#IMPORTERS["github"] = { +# "active": True, +# "client_id": "XXXXXX_get_a_valid_client_id_from_github_XXXXXX", +# "client_secret": "XXXXXX_get_a_valid_client_secret_from_github_XXXXXX" +#} + +# Configuration for the Trello importer +# Remember to enable it in the front client too. +#IMPORTERS["trello"] = { +# "active": True, # Enable or disable the importer +# "api_key": "XXXXXX_get_a_valid_api_key_from_trello_XXXXXX", +# "secret_key": "XXXXXX_get_a_valid_secret_key_from_trello_XXXXXX" +#} + +# Configuration for the Jira importer +# Remember to enable it in the front client too. +#IMPORTERS["jira"] = { +# "active": True, # Enable or disable the importer +# "consumer_key": "XXXXXX_get_a_valid_consumer_key_from_jira_XXXXXX", +# "cert": "XXXXXX_get_a_valid_cert_from_jira_XXXXXX", +# "pub_cert": "XXXXXX_get_a_valid_pub_cert_from_jira_XXXXXX" +#} diff --git a/roles/taiga/templates/events/env.j2 b/roles/taiga/templates/events/env.j2 new file mode 100644 index 0000000..0f85557 --- /dev/null +++ b/roles/taiga/templates/events/env.j2 @@ -0,0 +1,4 @@ +RABBITMQ_URL="amqp://{{ taiga_amqp_user }}:{{ taiga_amqp_pass }}@{{ taiga_amqp_server }}:{{ taiga_amqp_port }}/{{ taiga_amqp_vhost }}" +SECRET="{{ taiga_secret_key }}" +WEB_SOCKET_SERVER_PORT={{ taiga_ports['events'] }} +APP_PORT=3023 diff --git a/roles/taiga/templates/front/conf.json.j2 b/roles/taiga/templates/front/conf.json.j2 new file mode 100644 index 0000000..169a426 --- /dev/null +++ b/roles/taiga/templates/front/conf.json.j2 @@ -0,0 +1,29 @@ +{ + "api": "{{ taiga_public_url }}/api/v1/", + "eventsUrl": "{{ taiga_public_url | regex_replace('^http','ws') }}/events", + "baseHref": "{{ (taiga_public_url | urlsplit('path') == '') | ternary('/', taiga_public_url | urlsplit('path')) }}", + "eventsMaxMissedHeartbeats": 5, + "eventsHeartbeatIntervalTime": 60000, + "eventsReconnectTryInterval": 10000, + "debug": false, + "debugInfo": false, + "defaultLanguage": "en", + "themes": ["taiga"], + "defaultTheme": "taiga", + "defaultLoginEnabled": true, + "publicRegisterEnabled": {{ taiga_user_registration | ternary('true', 'false') }}, + "feedbackEnabled": true, + "supportUrl": "https://resources.taiga.io", + "privacyPolicyUrl": null, + "termsOfServiceUrl": null, + "maxUploadFileSize": {{ taiga_max_upload_file_size }}, + "contribPlugins": [], + "tagManager": { "accountId": null }, + "tribeHost": null, + "enableAsanaImporter": false, + "enableGithubImporter": false, + "enableJiraImporter": false, + "enableTrelloImporter": false, + "gravatar": false, + "rtlLanguages": ["ar", "fa", "he"] +} diff --git a/roles/taiga/templates/nginx.conf.j2 b/roles/taiga/templates/nginx.conf.j2 new file mode 100644 index 0000000..e0abe64 --- /dev/null +++ b/roles/taiga/templates/nginx.conf.j2 @@ -0,0 +1,76 @@ +server { + listen 443 ssl http2; + server_name {{ taiga_public_url | urlsplit('hostname') }}; + + large_client_header_buffers 4 32k; + client_max_body_size {{ taiga_max_upload_file_size }}M; + charset utf-8; + + # Frontend + location / { + alias {{ taiga_root_dir }}/app/front/dist/; + index index.html; + try_files $uri $uri/ index.html =404; + } + + # API + location /api/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:{{ taiga_ports['back'] }}/api/; + proxy_redirect off; + } + + # Admin + location /admin/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:{{ taiga_ports['back'] }}/admin/; + proxy_redirect off; + } + + # Static files + location /static/ { + alias {{ taiga_root_dir }}/app/back/static/; + } + + # Media + location /_protected/ { + internal; + alias {{ taiga_root_dir }}/data/media/; + add_header Content-disposition "attachment"; + } + + # Unprotected section + location /media/exports/ { + alias {{ taiga_root_dir }}/data/media/exports/; + add_header Content-disposition "attachment"; + } + + location /media/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:{{ taiga_ports['protected'] }}/; + proxy_redirect off; + } + + # Events + location /events { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_connect_timeout 7d; + proxy_send_timeout 7d; + proxy_read_timeout 7d; + proxy_pass http://127.0.0.1:{{ taiga_ports['events'] }}/events; + } +} diff --git a/roles/taiga/templates/post-backup.j2 b/roles/taiga/templates/post-backup.j2 new file mode 100644 index 0000000..d391c7b --- /dev/null +++ b/roles/taiga/templates/post-backup.j2 @@ -0,0 +1,5 @@ +#!/bin/sh + +set -eo pipefail + +rm -f {{ taiga_root_dir }}/backup/* diff --git a/roles/taiga/templates/pre-backup.j2 b/roles/taiga/templates/pre-backup.j2 new file mode 100644 index 0000000..85d28bb --- /dev/null +++ b/roles/taiga/templates/pre-backup.j2 @@ -0,0 +1,13 @@ +#!/bin/sh + +set -eo pipefail + +PGPASSWORD='{{ taiga_db_pass }}' /usr/pgsql-14/bin/pg_dump \ + --clean \ + --create \ + --username={{ taiga_db_user | quote }} \ + --host={{ taiga_db_server | quote }} \ + --port={{ taiga_db_port }} \ + {{ taiga_db_name | quote }} | \ + zstd -c > {{ taiga_root_dir }}/backup/{{ taiga_db_name | quote }}.sql.zst + diff --git a/roles/taiga/templates/protected/env.j2 b/roles/taiga/templates/protected/env.j2 new file mode 100644 index 0000000..797339b --- /dev/null +++ b/roles/taiga/templates/protected/env.j2 @@ -0,0 +1,3 @@ +SECRET_KEY={{ taiga_secret_key }} +MAX_AGE=300 +TAIGA_SUBPATH={{ taiga_public_url | urlsplit('path') }} diff --git a/roles/taiga/templates/taiga-async.service.j2 b/roles/taiga/templates/taiga-async.service.j2 new file mode 100644 index 0000000..71e3af4 --- /dev/null +++ b/roles/taiga/templates/taiga-async.service.j2 @@ -0,0 +1,25 @@ +[Unit] +Description=Taiga async +After=network.target rabbitmq-server.service + +[Service] +User={{ taiga_user }} +Group={{ taiga_user }} +Environment=PYTHONUNBUFFERED=true +Environment=DJANGO_SETTINGS_MODULE=settings.config +WorkingDirectory={{ taiga_root_dir }}/app/back +ExecStart={{ taiga_root_dir }}/venv/bin/celery -A taiga.celery worker -B --concurrency 4 -l INFO --schedule={{ taiga_root_dir }}/data/celerybeat-schedule +PrivateTmp=yes +PrivateDevices=yes +ProtectSystem=full +ProtectHome=yes +NoNewPrivileges=yes +MemoryLimit=1024M +SyslogIdentifier=taiga-async +Restart=on-failure +StartLimitInterval=0 +RestartSec=30 + +[Install] +WantedBy=multi-user.target + diff --git a/roles/taiga/templates/taiga-back.service.j2 b/roles/taiga/templates/taiga-back.service.j2 new file mode 100644 index 0000000..1b06d06 --- /dev/null +++ b/roles/taiga/templates/taiga-back.service.j2 @@ -0,0 +1,24 @@ +[Unit] +Description=Taiga backend +After=network.target rabbitmq-server.service + +[Service] +User={{ taiga_user }} +Group={{ taiga_user }} +Environment=PYTHONUNBUFFERED=true +Environment=DJANGO_SETTINGS_MODULE=settings.config +WorkingDirectory={{ taiga_root_dir }}/app/back +ExecStart={{ taiga_root_dir }}/venv/bin/gunicorn --workers 4 --timeout 60 --log-level=info --access-logfile - --bind 127.0.0.1:{{ taiga_ports['back'] }} taiga.wsgi +PrivateTmp=yes +PrivateDevices=yes +ProtectSystem=full +ProtectHome=yes +NoNewPrivileges=yes +MemoryLimit=1024M +SyslogIdentifier=taiga-back +Restart=on-failure +StartLimitInterval=0 +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/roles/taiga/templates/taiga-events.service.j2 b/roles/taiga/templates/taiga-events.service.j2 new file mode 100644 index 0000000..f078235 --- /dev/null +++ b/roles/taiga/templates/taiga-events.service.j2 @@ -0,0 +1,23 @@ +[Unit] +Description=Taiga events +After=network.target rabbitmq-server.service + +[Service] +User={{ taiga_user }} +Group={{ taiga_user }} +WorkingDirectory={{ taiga_root_dir }}/app/events +ExecStart=/bin/npm run start:production +PrivateTmp=yes +PrivateDevices=yes +ProtectSystem=full +ProtectHome=yes +NoNewPrivileges=yes +MemoryLimit=512M +SyslogIdentifier=taiga-events +Restart=on-failure +StartLimitInterval=0 +RestartSec=30 + +[Install] +WantedBy=multi-user.target + diff --git a/roles/taiga/templates/taiga-protected.service.j2 b/roles/taiga/templates/taiga-protected.service.j2 new file mode 100644 index 0000000..b35146b --- /dev/null +++ b/roles/taiga/templates/taiga-protected.service.j2 @@ -0,0 +1,23 @@ +[Unit] +Description=Taiga protected +After=network.target rabbitmq-server.service + +[Service] +User={{ taiga_user }} +Group={{ taiga_user }} +Environment=PYTHONUNBUFFERED=true +WorkingDirectory={{ taiga_root_dir }}/app/protected +ExecStart={{ taiga_root_dir }}/venv/bin/gunicorn --workers 4 --timeout 60 --log-level=info --access-logfile - --bind 127.0.0.1:{{ taiga_ports['protected'] }} server:app +PrivateTmp=yes +PrivateDevices=yes +ProtectSystem=full +ProtectHome=yes +NoNewPrivileges=yes +MemoryLimit=512M +SyslogIdentifier=taiga-protected +Restart=on-failure +StartLimitInterval=0 +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/roles/taiga/vars/RedHat-8.yml b/roles/taiga/vars/RedHat-8.yml new file mode 100644 index 0000000..b70f80f --- /dev/null +++ b/roles/taiga/vars/RedHat-8.yml @@ -0,0 +1,31 @@ +--- + +taiga_packages: + - git + - tar + - zstd + - python39-pip + - python39-devel + - python39-wheel + - autoconf + - flex + - bison + - libjpeg-turbo-devel + - freetype-devel + - zlib-devel + - cppzmq-devel + - gdbm-devel + - ncurses-devel + - automake + - libtool + - curl + - tmux + - gettext + - postgresql14 + - postgresql-devel + - libpq-devel + - libxml2-devel + - libxslt-devel + - openssl-devel + - libffi-devel + - policycoreutils