diff --git a/roles/penpot/defaults/main.yml b/roles/penpot/defaults/main.yml new file mode 100644 index 0000000..4c5374c --- /dev/null +++ b/roles/penpot/defaults/main.yml @@ -0,0 +1,81 @@ +--- + +# Penpot version to deploy +penpot_version: 1.10.1-beta +# SHould ansible manage upgrades. If False, only the initial install will be done +penpot_manage_upgrade: True + +# Where will penpot be installed +penpot_root_dir: /opt/penpot +# URL of the archive +penpot_archive_url: https://github.com/penpot/penpot/archive/refs/tags/{{ penpot_version }}.tar.gz +# Expected sha256 of the archive +penpot_archive_sha256: 63392c007ac7df2e723731961741a271c6e752df5c592165f55f11fea74ad6f8 +# User under which penpot will run. Will be created +penpot_user: penpot + +# Public URL where penpot will be avaoilable to users +penpot_public_url: https://{{ inventory_hostname }} + +# Ports used by penpot components +# Note that those ports will bind on localhost only, and penpot will be +# exposed by an nginx instance. If you want to restrict penpot access at the firewall level +# you have to set nginx_src_ip +penpot_ports: + backend: 6060 + exporter: 6061 + srepl: 6062 + +# Postgres database settings +penpot_db_server: "{{ pg_server | default('localhost') }}" +penpot_db_port: 5432 +penpot_db_name: penpot +penpot_db_user: penpot +# If the password is not defined, a random one will be created and stored un {{ penpot_root_dir }}/meta/ansible_dbpass +# penpot_db_pass: S3cr3t. + +# Penpot uses a redis server to handle notifications +# Note: redis will be installed if this URL points on localhost +penpot_redis_url: redis://localhost/0 + +# Allow user registration ? Note that oidc auth requires registration to be enabled +penpot_allow_user_registration: "{{ penpot_oidc_auth | ternary(True, False) }}" +# You can restrict registrations to some domains +penpot_user_registration_allowed_domains: [ "{{ ansible_domain }}" ] + +# OIDC auth +penpot_oidc_auth: False +penpot_oidc_base_url: https://sso.{{ ansible_domain }}/oauth2 +penpot_oidc_auth_url: "{{ penpot_oidc_base_url }}/authorize" +penpot_oidc_user_url: "{{ penpot_oidc_base_url }}/userinfo" +penpot_oidc_token_url: "{{ penpot_oidc_base_url }}/token" +penpot_oidc_client_id: penpot +# The oidc secret must be set +# penpot_oidc_client_secret: S3cr3t. +penpot_oidc_scope: openid email profile + +# LDAP auth +penpot_ldap_auth: "{{ (ldap_auth | default(False) or ad_auth | default(False)) | ternary(True, False) }}" +penpot_ldap_server: "{{ ldap_uri | default('ldap://ldap.' ~ ansible_domain) | urlsplit('hostname') }}" +penpot_ldap_port: "{{ (ldap_uri | default('ldap://ldap.' ~ ansible_domain) | urlsplit('port') == '') | ternary(ldap_uri | default('ldap://ldap.' ~ ansible_domain) | urlsplit('port'), '389') }}" +penpot_ldap_ssl: "{{ (penpot_ldap_port == 636) | ternary(True, False) }}" +penpot_ldap_tls: "{{ penpot_ldap_ssl | ternary(False, True) }}" +penpot_ldap_base_dn: "{{ (ad_ldap_user_search_base is defined) | ternary(ad_ldap_user_search_base,(ldap_user_base is defined) | ternary(ldap_user_base,(ad_auth | default(False) | ternary('DC=' + ad_realm | default(samba_realm) | default(ansible_domain) | regex_replace('\\.',',DC='), 'dc=' ~ ansible_domain | regex_replace('\\.',',dc='))))) }}" +penpot_ldap_search_filter: "{{ ad_auth | ternary('(&(sAMAccountName=:username)(objectClass=user)(objectCatagory=person)(!(useraccountcontrol:1.2.840.113556.1.4.803:=2)))', '(&(uid=:username)(objectClass=inetOrgPerson))') }}" +# If auth is needed, set penpot_ldap_bind_dn and penpot_ldap_bind_pass +# penpot_ldap_bin_dn: CN=Penpot, OU=Apps, DC=example, DC=org +# penpot_ldap_bind_pass: S3cr3t. +penpot_ldap_attr_username: "{{ ad_auth | default(False) | ternary('sAMAccountName', 'uid') }}" +penpot_ldap_attr_email: mail +penpot_ldap_attr_fullname: cn +penpot_ldap_attr_photo: jpegPhoto + +# Email settings +penpot_email_from: no-reply@{{ ansible_domain }} +penpot_smtp_server: localhost +penpot_smtp_port: 25 +penpot_smtp_tls: "{{ (penpot_smtp_port == 587) | ternary(True, False) }}" +penpot_smtp_ssl: "{{ (penpot_smtp_port == 465) | ternary(True, False) }}" +# You can set user and password if needed +# penpot_smtp_user: penpot@example.org +# penpot_smtp_pass: S3cr3t. diff --git a/roles/penpot/handlers/main.yml b/roles/penpot/handlers/main.yml new file mode 100644 index 0000000..602fb7c --- /dev/null +++ b/roles/penpot/handlers/main.yml @@ -0,0 +1,7 @@ +--- + +- name: restart penpot + service: name={{ item }} state=restarted + loop: + - penpot-server + - penpot-exporter diff --git a/roles/penpot/meta/main.yml b/roles/penpot/meta/main.yml new file mode 100644 index 0000000..93ba808 --- /dev/null +++ b/roles/penpot/meta/main.yml @@ -0,0 +1,10 @@ +--- + +dependencies: + - role: babashka + - role: nodejs + - role: nginx + - role: redis_server + when: penpot_redis_url | urlsplit('hostname') in ['localhost', '127.0.0.1'] + - role: postgresql_server + when: penpot_db_server in ['localhost', '127.0.0.1'] diff --git a/roles/penpot/tasks/archive_post.yml b/roles/penpot/tasks/archive_post.yml new file mode 100644 index 0000000..576930b --- /dev/null +++ b/roles/penpot/tasks/archive_post.yml @@ -0,0 +1,11 @@ +--- + +- name: Compress previous version + command: tar cf {{ penpot_root_dir }}/archives/{{ penpot_current_version }}.tar.zst --use-compress-program=zstd ./ + args: + chdir: "{{ penpot_root_dir }}/archives/{{ penpot_current_version }}" + warn: False + environment: + ZSTD_CLEVEL: 10 + ZSTD_NBTHREADS: 0 + tags: penpot diff --git a/roles/penpot/tasks/archive_pre.yml b/roles/penpot/tasks/archive_pre.yml new file mode 100644 index 0000000..788c678 --- /dev/null +++ b/roles/penpot/tasks/archive_pre.yml @@ -0,0 +1,37 @@ +--- + +- name: Create archive dir + file: path={{ penpot_root_dir }}/archives/{{ penpot_current_version }} state=directory + tags: penpot + +- name: Stop service during upgrade + service: name={{ item }} state=stopped + loop: + - penpot-server + - penpot-exporter + tags: penpot + +- name: Archive current version + synchronize: + src: "{{ penpot_root_dir }}/{{ item }}" + dest: "{{ penpot_root_dir }}/archives/{{ penpot_current_version }}/" + delete: True + compress: False + delegate_to: "{{ inventory_hostname }}" + loop: + - backend + - frontend + tags: penpot + +- name: Dump the database + command: > + /usr/pgsql-14/bin/pg_dump + --clean + --create + --host={{ penpot_db_server | quote }} + --port={{ penpot_db_port | quote }} + --username={{ penpot_db_user | quote }} {{ penpot_db_name | quote }} + --file="{{ penpot_root_dir }}/archives/{{ penpot_current_version }}/{{ penpot_db_name }}.sql" + environment: + - PGPASSWORD: "{{ penpot_db_pass }}" + tags: penpot diff --git a/roles/penpot/tasks/cleanup.yml b/roles/penpot/tasks/cleanup.yml new file mode 100644 index 0000000..ef28dca --- /dev/null +++ b/roles/penpot/tasks/cleanup.yml @@ -0,0 +1,9 @@ +--- + +- name: Remove tmp and obsolete files + file: path={{ item }} state=absent + loop: + - "{{ penpot_root_dir }}/tmp/penpot-{{ penpot_version }}.tar.gz" + - "{{ penpot_root_dir }}/tmp/penpot-{{ penpot_version }}" + - "{{ penpot_root_dir }}/archives/{{ penpot_current_version }}" + tags: penpot diff --git a/roles/penpot/tasks/conf.yml b/roles/penpot/tasks/conf.yml new file mode 100644 index 0000000..30a8352 --- /dev/null +++ b/roles/penpot/tasks/conf.yml @@ -0,0 +1,15 @@ +--- + +- name: Deploy backend configuration + template: src=env.j2 dest={{ penpot_root_dir }}/etc/env owner=root group={{ penpot_user }} mode=640 + notify: restart penpot + tags: penpot + +- name: Deploy frontend configuration + template: src=config.js.j2 dest={{ penpot_root_dir }}/frontend/js/config.js + tags: penpot + +- name: Deploy nginx configuration + template: src=nginx.conf.j2 dest=/etc/nginx/ansible_conf.d/10-penpot.conf + notify: reload nginx + tags: penpot diff --git a/roles/penpot/tasks/directories.yml b/roles/penpot/tasks/directories.yml new file mode 100644 index 0000000..f8cb2e5 --- /dev/null +++ b/roles/penpot/tasks/directories.yml @@ -0,0 +1,37 @@ +--- + +- name: Create directories + file: path={{ item.dir }} state=directory owner={{ item.owner | default(omit) }} group={{ item.group | default(omit) }} mode={{ item.mode | default(omit) }} + loop: + - dir: "{{ penpot_root_dir }}" + - dir: "{{ penpot_root_dir }}/backend" + - dir: "{{ penpot_root_dir }}/frontend" + - dir: "{{ penpot_root_dir }}/exporter" + - dir: "{{ penpot_root_dir }}/meta" + owner: root + group: root + mode: 700 + - dir: "{{ penpot_root_dir }}/backup" + owner: root + group: root + mode: 700 + - dir: "{{ penpot_root_dir }}/archives" + owner: root + group: root + mode: 700 + - dir: "{{ penpot_root_dir }}/tmp" + owner: "{{ penpot_user }}" + group: "{{ penpot_user }}" + mode: 700 + - dir: "{{ penpot_root_dir }}/data" + owner: "{{ penpot_user }}" + group: "{{ penpot_user }}" + mode: 770 + - dir: "{{ penpot_root_dir }}/data/assets" + owner: "{{ penpot_user }}" + group: "{{ penpot_user }}" + - dir: "{{ penpot_root_dir }}/etc" + owner: root + group: "{{ penpot_user }}" + mode: 750 + tags: penpot diff --git a/roles/penpot/tasks/facts.yml b/roles/penpot/tasks/facts.yml new file mode 100644 index 0000000..f86f701 --- /dev/null +++ b/roles/penpot/tasks/facts.yml @@ -0,0 +1,29 @@ +--- + +# 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: penpot + +# Detect installed version (if any) +- block: + - import_tasks: ../includes/webapps_set_install_mode.yml + vars: + - root_dir: "{{ penpot_root_dir }}" + - version: "{{ penpot_version }}" + - set_fact: penpot_install_mode={{ (install_mode == 'upgrade' and not penpot_manage_upgrade) | ternary('none',install_mode) }} + - set_fact: penpot_current_version={{ current_version | default('') }} + tags: penpot + +# Create a random pass for the DB if needed +- block: + - import_tasks: ../includes/get_rand_pass.yml + vars: + - pass_file: "{{ penpot_root_dir }}/meta/ansible_dbpass" + - set_fact: penpot_db_pass={{ rand_pass }} + when: penpot_db_pass is not defined + tags: penpot diff --git a/roles/penpot/tasks/install.yml b/roles/penpot/tasks/install.yml new file mode 100644 index 0000000..f84eec8 --- /dev/null +++ b/roles/penpot/tasks/install.yml @@ -0,0 +1,127 @@ +--- + +- name: Install system dependencies + package: name={{ penpot_packages }} + tags: penpot + +- name: Install nodejs tools + npm: name={{ item }} global=True + loop: + - yarn + - sfnt2woff + tags: penpot + +- when: penpot_install_mode != 'none' + block: + + - name: Download penpot + get_url: + url: "{{ penpot_archive_url }}" + dest: "{{ penpot_root_dir }}/tmp/" + checksum: sha256:{{ penpot_archive_sha256 }} + + - name: Extract penpot archive + unarchive: + src: "{{ penpot_root_dir }}/tmp/penpot-{{ penpot_version }}.tar.gz" + dest: "{{ penpot_root_dir }}/tmp/" + remote_src: True + + - name: Build penpot backend + command: bb ./scripts/build + args: + chdir: "{{ penpot_root_dir }}/tmp/penpot-{{ penpot_version }}/backend" + + - name: Build penpot frontend + command: ./scripts/build + args: + chdir: "{{ penpot_root_dir }}/tmp/penpot-{{ penpot_version }}/frontend" + environment: + CURRENT_HASH: "{{ penpot_version }}" + + - name: Build penpot exporter + command: ./scripts/build + args: + chdir: "{{ penpot_root_dir }}/tmp/penpot-{{ penpot_version }}/exporter" + + - name: Install penpot exporter dependencies + command: yarn install + args: + chdir: "{{ penpot_root_dir }}/tmp/penpot-{{ penpot_version }}/exporter/target" + + become_user: "{{ penpot_user }}" + tags: penpot + +- when: penpot_install_mode != 'none' + block: + + - name: Install penpot backend and frontend + synchronize: + src: "{{ penpot_root_dir }}/tmp/penpot-{{ penpot_version }}/{{ item }}/target/dist/" + dest: "{{ penpot_root_dir }}/{{ item }}/" + delete: True + compress: False + delegate_to: "{{ inventory_hostname }}" + loop: + - backend + - frontend + + - name: Install penpot exporter + synchronize: + src: "{{ penpot_root_dir }}/tmp/penpot-{{ penpot_version }}/exporter/target/" + dest: "{{ penpot_root_dir }}/exporter/" + delete: True + compress: False + delegate_to: "{{ inventory_hostname }}" + + - name: Set permissions + shell: | + setfacl -R -k -b {{ penpot_root_dir }}/ + setfacl -m u:nginx:x {{ penpot_root_dir }}/ + setfacl -m u:nginx:x {{ penpot_root_dir }}/data + setfacl -R -m u:nginx:rX {{ penpot_root_dir }}/data/assets + + tags: penpot + +- block: + - name: Create the PostgreSQL role + postgresql_user: + db: postgres + name: "{{ penpot_db_user }}" + password: "{{ penpot_db_pass }}" + login_host: "{{ penpot_db_server }}" + login_user: sqladmin + login_password: "{{ pg_admin_pass }}" + + - name: Create the PostgreSQL database + postgresql_db: + name: "{{ penpot_db_name }}" + encoding: UTF-8 + template: template0 + owner: "{{ penpot_db_user }}" + login_host: "{{ penpot_db_server }}" + login_user: sqladmin + login_password: "{{ pg_admin_pass }}" + + tags: penpot + +- name: Deploy systemd units + template: src={{ item }}.j2 dest=/etc/systemd/system/{{ item }} + loop: + - penpot-server.service + - penpot-exporter.service + register: penpot_units + notify: restart penpot + tags: penpot + +- name: Reload systemd + systemd: daemon_reload=True + when: penpot_units.results | selectattr('changed','equalto',True) | list | length > 0 + tags: penpot + +- name: Install backup hooks + template: src={{ item }}-backup.j2 dest=/etc/backup/{{ item }}.d/penpot mode=700 + loop: + - pre + - post + tags: penpot + diff --git a/roles/penpot/tasks/main.yml b/roles/penpot/tasks/main.yml new file mode 100644 index 0000000..9cd4608 --- /dev/null +++ b/roles/penpot/tasks/main.yml @@ -0,0 +1,14 @@ +--- + +- include: user.yml +- include: directories.yml +- include: facts.yml +- include: archive_pre.yml + when: penpot_install_mode == 'upgrade' +- include: install.yml +- include: conf.yml +- include: services.yml +- include: write_version.yml +- include: archive_post.yml + when: penpot_install_mode == 'upgrade' +- include: cleanup.yml diff --git a/roles/penpot/tasks/services.yml b/roles/penpot/tasks/services.yml new file mode 100644 index 0000000..83fad2a --- /dev/null +++ b/roles/penpot/tasks/services.yml @@ -0,0 +1,8 @@ +--- + +- name: Start and enable services + service: name={{ item }} state=started enabled=True + loop: + - penpot-server + - penpot-exporter + tags: penpot diff --git a/roles/penpot/tasks/user.yml b/roles/penpot/tasks/user.yml new file mode 100644 index 0000000..8a445e9 --- /dev/null +++ b/roles/penpot/tasks/user.yml @@ -0,0 +1,9 @@ +--- + +- name: Create user + user: + name: "{{ penpot_user }}" + home: "{{ penpot_root_dir }}" + system: True + shell: /sbin/nologin + tags: penpot diff --git a/roles/penpot/tasks/write_version.yml b/roles/penpot/tasks/write_version.yml new file mode 100644 index 0000000..ce78c99 --- /dev/null +++ b/roles/penpot/tasks/write_version.yml @@ -0,0 +1,5 @@ +--- + +- name: Write installed version + copy: content={{ penpot_version }} dest={{ penpot_root_dir }}/meta/ansible_version + tags: penpot diff --git a/roles/penpot/templates/config.js.j2 b/roles/penpot/templates/config.js.j2 new file mode 100644 index 0000000..993451e --- /dev/null +++ b/roles/penpot/templates/config.js.j2 @@ -0,0 +1,12 @@ +var penpotPublicURI = "{{ penpot_public_url }}"; +var penpotDemoWarning = false; +var penpotAllowDemoUsers = false; +{% if penpot_oidc_auth %} +var penpotOIDCClientID = "{{ penpot_oidc_client_id }}"; +{% endif %} +{% if penpot_ldap_auth %} +var penpotLoginWithLDAP = true; +{% endif %} +var penpotRegistrationEnabled = {{ penpot_allow_user_registration | ternary('true', 'false') }}; +var penpotAnalyticsEnabled = false; +var penpotFlags = "{{ penpot_allow_user_registration | ternary('enable-registration', '') }}"; diff --git a/roles/penpot/templates/env.j2 b/roles/penpot/templates/env.j2 new file mode 100644 index 0000000..0258722 --- /dev/null +++ b/roles/penpot/templates/env.j2 @@ -0,0 +1,78 @@ + +PENPOT_HTTP_SERVER_PORT={{ penpot_ports['backend'] }} +PENPOT_SREPL_PORT={{ penpot_ports['srepl'] }} + +# Should be set to the public domain where penpot is going to be served. +PENPOT_PUBLIC_URI={{ penpot_public_url }} + +# Standard database connection parameters (only postgresql is supported): +PENPOT_DATABASE_URI=postgresql://{{ penpot_db_server }}:{{ penpot_db_port }}/{{ penpot_db_name }} +PENPOT_DATABASE_USERNAME={{ penpot_db_user }} +PENPOT_DATABASE_PASSWORD={{ penpot_db_pass }} + +# Redis is used for the websockets notifications. +PENPOT_REDIS_URI={{ penpot_redis_url }} + +# By default, files uploaded by users are stored in local filesystem. But it +# can be configured to store in AWS S3 or completely in de the database. +# Storing in the database makes the backups more easy but will make access to +# media less performant. +ASSETS_STORAGE_BACKEND=assets-fs +PENPOT_STORAGE_ASSETS_FS_DIRECTORY={{ penpot_root_dir }}/data/assets + +# Telemetry. When enabled, a periodical process will send anonymous data about +# this instance. Telemetry data will enable us to learn on how the application +# is used, based on real scenarios. If you want to help us, please leave it +# enabled. +PENPOT_TELEMETRY_ENABLED=false + +# Email sending configuration. By default, emails are printed in the console, +# but for production usage is recommended to setup a real SMTP provider. Emails +# are used to confirm user registrations. +PENPOT_SMTP_ENABLED=true +PENPOT_SMTP_DEFAULT_FROM={{ penpot_email_from }} +PENPOT_SMTP_DEFAULT_REPLY_TO={{ penpot_email_from }} +PENPOT_SMTP_HOST={{ penpot_smtp_server }} +PENPOT_SMTP_PORT={{ penpot_smtp_port }} +{% if penpot_smtp_user is defined and penpot_smtp_pass is defined %} +PENPOT_SMTP_USERNAME={{ penpot_smtp_user }} +PENPOT_SMTP_PASSWORD={{ penpot_smtp_pass }} +{% endif %} +PENPOT_SMTP_TLS={{ penpot_smtp_tls | ternary('true','false') }} +PENPOT_SMTP_SSL={{ penpot_smtp_ssl | ternary('true','false') }} + +# Feature flags. Right now they are only affect frontend, but in +# future release they will affect to both backend and frontend. +PENPOT_FLAGS="{{ penpot_allow_user_registration | ternary('enable-registration', '') }}" + +# Comma separated list of allowed domains to register. Empty to allow all. +PENPOT_REGISTRATION_DOMAIN_WHITELIST="{{ penpot_user_registration_allowed_domains | join(',') }}" + +## Authentication providers +{% if penpot_oidc_auth %} +PENPOT_OIDC_BASE_URI={{ penpot_oidc_base_url }} +PENPOT_OIDC_USER_URL={{ penpot_oidc_user_url }} +PENPOT_OIDC_AUTH_URL={{ penpot_oidc_auth_url }} +PENPOT_OIDC_TOKEN_URL={{ penpot_oidc_token_url }} +PENPOT_OIDC_SCOPE={{ penpot_oidc_scope }} +PENPOT_OIDC_CLIENT_ID={{ penpot_oidc_client_id }} +PENPOT_OIDC_CLIENT_SECRET={{ penpot_oidc_client_secret }} +{% endif %} + +{% if penpot_ldap_auth %} +# LDAP +PENPOT_LDAP_HOST={{ penpot_ldap_server }} +PENPOT_LDAP_PORT={{ penpot_ldap_port }} +PENPOT_LDAP_SSL={{ penpot_ldap_ssl | ternary('true', 'false') }} +PENPOT_LDAP_STARTTLS={{ penpot_ldap_tls | ternary('true', 'false') }} +PENPOT_LDAP_BASE_DN={{ penpot_ldap_base_dn }} +{% if penpot_ldap_bind_dn is defined and penpot_ldap_bind_pass is defined %} +PENPOT_LDAP_BIND_DN={{ penpot_ldap_bind_dn }} +PENPOT_LDAP_BIND_PASSWORD={{ penpot_ldap_bind_pass }} +{% endif %} +PENPOT_LDAP_ATTRS_USERNAME={{ penpot_ldap_attr_username }} +PENPOT_LDAP_ATTRS_EMAIL={{ penpot_ldap_attr_email }} +PENPOT_LDAP_ATTRS_FULLNAME={{ penpot_ldap_attr_fullname }} +PENPOT_LDAP_ATTRS_PHOTO={{ penpot_ldap_attr_photo }} +PENPOT_LOGIN_WITH_LDAP=true +{% endif %} diff --git a/roles/penpot/templates/nginx.conf.j2 b/roles/penpot/templates/nginx.conf.j2 new file mode 100644 index 0000000..99c4977 --- /dev/null +++ b/roles/penpot/templates/nginx.conf.j2 @@ -0,0 +1,76 @@ +server { + listen 443 ssl http2; + server_name {{ penpot_public_url | urlsplit('hostname') }}; + + client_max_body_size 50M; + charset utf-8; + + proxy_http_version 1.1; + 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; + + etag off; + root {{ penpot_root_dir }}/frontend; + + location ~* \.(js|css).*$ { + add_header Cache-Control "max-age=86400" always; # 24 hours + } + + location ~* \.(html).*$ { + add_header Cache-Control "no-cache, max-age=0" always; + } + + location /api { + proxy_pass http://localhost:{{ penpot_ports['backend'] }}/api; + } + + location /dbg { + proxy_pass http://localhost:{{ penpot_ports['backend'] }}/dbg; + } + + location /export { + proxy_pass http://localhost:{{ penpot_ports['exporter'] }}; + } + + location /ws/notifications { + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_pass http://localhost:{{ penpot_ports['backend'] }}/ws/notifications; + } + + location @handle_redirect { + set $redirect_uri "$upstream_http_location"; + set $redirect_host "$upstream_http_x_host"; + set $redirect_cache_control "$upstream_http_cache_control"; + + proxy_buffering off; + + proxy_set_header Host "$redirect_host"; + proxy_hide_header etag; + proxy_hide_header x-amz-id-2; + proxy_hide_header x-amz-request-id; + proxy_hide_header x-amz-meta-server-side-encryption; + proxy_hide_header x-amz-server-side-encryption; + proxy_pass $redirect_uri; + + add_header x-internal-redirect "$redirect_uri"; + add_header x-cache-control "$redirect_cache_control"; + add_header cache-control "$redirect_cache_control"; + } + + location /assets { + proxy_pass http://localhost:{{ penpot_ports['backend'] }}/assets; + recursive_error_pages on; + proxy_intercept_errors on; + error_page 301 302 307 = @handle_redirect; + } + + location /internal/assets { + internal; + alias {{ penpot_root_dir }}/data/assets; + add_header x-internal-redirect "$upstream_http_x_accel_redirect"; + } +} diff --git a/roles/penpot/templates/penpot-exporter.service.j2 b/roles/penpot/templates/penpot-exporter.service.j2 new file mode 100644 index 0000000..8b3ae00 --- /dev/null +++ b/roles/penpot/templates/penpot-exporter.service.j2 @@ -0,0 +1,32 @@ +[Unit] +Description=Penpot exporter +After=syslog.target network.target + +[Service] +Type=simple +User={{ penpot_user }} +WorkingDirectory={{ penpot_root_dir }}/exporter +Environment=PENPOT_HTTP_SERVER_PORT={{ penpot_ports['exporter'] }} +ExecStart=/bin/node {{ penpot_root_dir }}/exporter/app.js +PrivateTmp=yes +NoNewPrivileges=true +Restart=on-failure +MemoryLimit=512M +SyslogIdentifier=penpot-exporter +Restart=on-failure +StartLimitInterval=0 +RestartSec=30 +PrivateDevices=true +ProtectControlGroups=true +ProtectHome=true +ProtectSystem=full +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectSystem=strict +RestrictRealtime=true +RestrictNamespaces=true +ReadWritePaths=/run {{ penpot_root_dir }}/data +LockPersonality=true + +[Install] +WantedBy=multi-user.target diff --git a/roles/penpot/templates/penpot-server.service.j2 b/roles/penpot/templates/penpot-server.service.j2 new file mode 100644 index 0000000..48f1d64 --- /dev/null +++ b/roles/penpot/templates/penpot-server.service.j2 @@ -0,0 +1,33 @@ +[Unit] +Description=Penpot server +After=syslog.target network.target + +[Service] +Type=simple +User={{ penpot_user }} +WorkingDirectory={{ penpot_root_dir }}/backend +EnvironmentFile={{ penpot_root_dir }}/etc/env +ExecStart={{ penpot_root_dir }}/backend/run.sh +SuccessExitStatus=143 +PrivateTmp=yes +NoNewPrivileges=true +Restart=on-failure +MemoryLimit=2048M +SyslogIdentifier=penpot-server +Restart=on-failure +StartLimitInterval=0 +RestartSec=30 +PrivateDevices=true +ProtectControlGroups=true +ProtectHome=true +ProtectSystem=full +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectSystem=strict +RestrictRealtime=true +RestrictNamespaces=true +ReadWritePaths=/run {{ penpot_root_dir }}/data +LockPersonality=true + +[Install] +WantedBy=multi-user.target diff --git a/roles/penpot/templates/post-backup.j2 b/roles/penpot/templates/post-backup.j2 new file mode 100644 index 0000000..0533974 --- /dev/null +++ b/roles/penpot/templates/post-backup.j2 @@ -0,0 +1,5 @@ +#!/bin/sh + +set -eo pipefail + +rm -f {{ penpot_root_dir }}/backup/{{ penpot_db_name }}.sql.zst diff --git a/roles/penpot/templates/pre-backup.j2 b/roles/penpot/templates/pre-backup.j2 new file mode 100644 index 0000000..f2e6170 --- /dev/null +++ b/roles/penpot/templates/pre-backup.j2 @@ -0,0 +1,12 @@ +#!/bin/sh + +set -eo pipefail + +PGPASSWORD='{{ penpot_db_pass }}' /usr/pgsql-14/bin/pg_dump \ + --clean \ + --create \ + --username={{ penpot_db_user | quote }} \ + --host={{ penpot_db_server | quote }} \ + --port={{ penpot_db_port }} \ + {{ penpot_db_name | quote }} | \ + zstd -c > {{ penpot_root_dir }}/backup/{{ penpot_db_name | quote }}.sql.zst diff --git a/roles/penpot/vars/RedHat-8.yml b/roles/penpot/vars/RedHat-8.yml new file mode 100644 index 0000000..ce6c7b7 --- /dev/null +++ b/roles/penpot/vars/RedHat-8.yml @@ -0,0 +1,16 @@ +--- + +penpot_packages: + - tar + - zstd + - postgresql14 + - java-11-openjdk-headless + - java-11-openjdk-devel + - ghostscript + - ImageMagick + - poppler-utils + - potrace + - netpbm + - liberation-fonts + - fontforge + - woff2-tools