diff --git a/webapps/mastodon/LISEZMOI.md b/webapps/mastodon/LISEZMOI.md new file mode 100644 index 00000000..6002a203 --- /dev/null +++ b/webapps/mastodon/LISEZMOI.md @@ -0,0 +1,67 @@ +mastodon +========= + +Ce rôle installe le serveur de Mastodon, une application de microblogage, libre et autohébergée. + +Notez qu'hormis le présent fichier LISEZMOI.md, tous les fichiers du rôle mastodon sont rédigés en anglais afin de suivre les conventions de la communauté Ansible, favoriser sa réutilisation et son amélioration, etc. Libre à vous cependant de faire appel à ce role dans un playbook rédigé principalement en français ou toute autre langue. + +Requis +------ + +... + +Variables du rôle +----------------- + +Plusieurs des valeurs par défaut dans defaults/main.yml doivent être changées soit directement dans defaults/main.yml ou mieux encore en les supplantant ailleurs, par exemple dans votre playbook (voir l'exemple ci-bas). + +Dépendances +------------ + +Ce rôle Ansible dépend des rôles suivants : + +- rbenv + +Exemple de playbook +------------------- + +``` +- name: "Déployer un serveur Mastodon" + hosts: + - all + vars: + # Supplanter ici les variables du rôle + mastodon_domains: ['votre-vrai-domaine.org'] + mastodon_service: 'mon-mastodon' + mastodon_db_host: 'localhost' + mastodon_db_user: "{{ service }}" + mastodon_db_name: "{{ service }}" + mastodon_db_password: 'zKEh-CHANGEZ-MOI-qIKc' + mastodon_app_secret_key_base: "" + mastodon_app_otp_secret: "" + mastodon_app_vapid_private_key: "" + mastodon_app_vapid_public_key: "" + mastodon_app_smtp_from_address: "mastodon@votre-vrai-domaine.org" + + pre_tasks: + - name: "Installer les rôles systèmes" + roles: + - { role: nodejs, nodejs_apt_version: 'node_16.x', nodejs_install_yarn: True } + - { role: postgresql } + - { role: redis } + - { role: elasticsearch } + - { role: nginx } + - { role: certbot } + roles: + - { role: webapps/mastodon , tags: "mastodon" } +``` + +Licence +------- + +GPLv3 + +Infos sur l'auteur +------------------ + +Mathieu Gauthier-Pilote, administrateur de systèmes chez Evolix. diff --git a/webapps/mastodon/README.md b/webapps/mastodon/README.md new file mode 100644 index 00000000..249322b0 --- /dev/null +++ b/webapps/mastodon/README.md @@ -0,0 +1,67 @@ +mastodon +========= + +This role installs or upgrades the server for Mastodon, a free and decentralized microblogging social network. + +FRENCH: Voir le fichier LISEZMOI.md pour le français. + +Requirements +------------ + +... + +Role Variables +-------------- + +Several of the default values in defaults/main.yml must be changed either directly in defaults/main.yml or better even by overwriting them somewhere else, for example in your playbook (see the example below). + +Dependencies +------------ + +This Ansible role depends on the following other roles: + +- rbenv + +Example Playbook +---------------- + +``` +- name: "Deploy a Mastodon server" + hosts: + - all + vars: + # Overwrite the role variable here + mastodon_domains: ['your-real-domain.org'] + mastodon_service: 'my-mastodon' + mastodon_db_host: 'localhost' + mastodon_db_user: "{{ service }}" + mastodon_db_name: "{{ service }}" + mastodon_db_password: 'zKEh-CHANGE-ME-qIKc' + mastodon_app_secret_key_base: "" + mastodon_app_otp_secret: "" + mastodon_app_vapid_private_key: "" + mastodon_app_vapid_public_key: "" + mastodon_app_smtp_from_address: "mastodon@your-real-domain.org" + + pre_tasks: + - name: "Install system roles" + roles: + - { role: nodejs, nodejs_apt_version: 'node_16.x', nodejs_install_yarn: True } + - { role: postgresql } + - { role: redis } + - { role: elasticsearch } + - { role: nginx } + - { role: certbot } + roles: + - { role: webapps/mastodon , tags: "mastodon" } +``` + +License +------- + +GPLv3 + +Author Information +------------------ + +Mathieu Gauthier-Pilote, sys. admin. at Evolix. diff --git a/webapps/mastodon/defaults/main.yml b/webapps/mastodon/defaults/main.yml new file mode 100644 index 00000000..68667691 --- /dev/null +++ b/webapps/mastodon/defaults/main.yml @@ -0,0 +1,24 @@ +--- +# defaults file for mastodon +mastodon_system_dep: "['imagemagick', 'ffmpeg', 'libpq-dev', 'libxml2-dev', 'libxslt1-dev', 'file', 'git-core', 'g++', 'libprotobuf-dev', 'protobuf-compiler', 'pkg-config', 'nodejs', 'gcc', 'autoconf', 'bison', 'build-essential', 'libssl-dev', 'libyaml-dev', 'libreadline6-dev', 'zlib1g-dev', 'libncurses5-dev', 'libffi-dev', 'libgdbm-dev', 'nginx', 'redis-server', 'redis-tools', 'postgresql', 'postgresql-contrib', 'certbot', 'python3-certbot-nginx', 'python3-psycopg2', 'libidn11-dev', 'libicu-dev', 'libjemalloc-dev']" +mastodon_domains: ['example.somedomain.org'] +mastodon_git_url: 'https://github.com/mastodon/mastodon.git' +mastodon_git_version: 'v4.0.2' +mastodon_ruby_version: '3.0.4' +mastodon_service: 'example' + +mastodon_db_host: 'localhost' +mastodon_db_user: "{{ mastodon_service }}" +mastodon_db_name: "{{ mastodon_service }}_production" +mastodon_db_password: 'CHANGE_ME' + +mastodon_app_secret_key_base: "" +mastodon_app_otp_secret: "" +mastodon_app_vapid_private_key: "" +mastodon_app_vapid_public_key: "" +mastodon_app_smtp_server: "127.0.0.1" +mastodon_app_smtp_port: "25" +mastodon_app_smtp_from_address: "example@somedomain.org" +mastodon_app_smtp_auth_method: "none" +mastodon_app_smtp_openssl_verify_mode: "none" +mastodon_app_es_enabled: "false" diff --git a/webapps/mastodon/handlers/main.yml b/webapps/mastodon/handlers/main.yml new file mode 100644 index 00000000..ba8d85b1 --- /dev/null +++ b/webapps/mastodon/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for mastodon diff --git a/webapps/mastodon/meta/main.yml b/webapps/mastodon/meta/main.yml new file mode 100644 index 00000000..c572acc9 --- /dev/null +++ b/webapps/mastodon/meta/main.yml @@ -0,0 +1,52 @@ +galaxy_info: + author: your name + description: your role description + company: your company (optional) + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + # issue_tracker_url: http://example.com/issue/tracker + + # Choose a valid license ID from https://spdx.org - some suggested licenses: + # - BSD-3-Clause (default) + # - MIT + # - GPL-2.0-or-later + # - GPL-3.0-only + # - Apache-2.0 + # - CC-BY-4.0 + license: license (GPL-2.0-or-later, MIT, etc) + + min_ansible_version: 2.1 + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + # platforms: + # - name: Fedora + # versions: + # - all + # - 25 + # - name: SomePlatform + # versions: + # - all + # - 1.0 + # - 7 + # - 99.99 + + galaxy_tags: [] + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] + # List your role dependencies here, one per line. Be sure to remove the '[]' above, + # if you add dependencies to this list. diff --git a/webapps/mastodon/tasks/main.yml b/webapps/mastodon/tasks/main.yml new file mode 100644 index 00000000..1f1cf6a3 --- /dev/null +++ b/webapps/mastodon/tasks/main.yml @@ -0,0 +1,245 @@ +--- +# tasks file for mastodon install + +- name: Install main system dependencies + ansible.builtin.apt: + name: "{{ mastodon_system_dep }}" + update_cache: yes + +- name: Install npm on Debian 12 + ansible.builtin.apt: + name: npm + when: ansible_distribution_major_version is version('12', '>=') + +- name: Install corepack via npm on Debian 12 + ansible.builtin.shell: npm install -g corepack + when: ansible_distribution_major_version is version('12', '>=') + +- name: Fix permissions for corepack + ansible.builtin.file: + path: /usr/local/lib/node_modules/ + state: directory + mode: o+rx + recurse: yes + when: ansible_distribution_major_version is version('12', '>=') + +- name: Enable yarn (via corepack) + ansible.builtin.shell: "corepack enable; yarn set version classic" + +- name: Add PostgreSQL user + community.postgresql.postgresql_user: + name: "{{ mastodon_db_user }}" + password: "{{ mastodon_db_password }}" + role_attr_flags: CREATEDB + become_user: postgres + +- name: Add UNIX account + ansible.builtin.user: + name: "{{ mastodon_service }}" + shell: /bin/bash +# umask: "0022" nécessite ansible-core 2.12 + +- name: Install Ruby for service user (rbenv) + include_role: + name: rbenv + vars: + - username: "{{ mastodon_service }}" + - rbenv_ruby_version: "{{ mastodon_ruby_version }}" + +- name: Clone Mastodon repo (git) + ansible.builtin.git: + repo: "{{ mastodon_git_url }}" + dest: "~/mastodon/" + version: "{{ mastodon_git_version | default(omit) }}" + #force: yes + update: yes + umask: '0022' + become_user: "{{ mastodon_service }}" + +- block: + - name: Install bundler + ansible.builtin.shell: ". ~/.profile && gem install bundler --no-document" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Install gem dependencies + ansible.builtin.shell: ". ~/.profile && bundle install --deployment --without development test -j{{ ansible_processor_count }}" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Install javascript dependencies + ansible.builtin.shell: ". ~/.profile && yarn install --pure-lockfile" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + become_user: "{{ mastodon_service }}" + +- name: Template .env.production configuration file + ansible.builtin.template: + src: "env.j2" + dest: "~{{ mastodon_service }}/mastodon/.env.production" + owner: "{{ mastodon_service }}" + group: "{{ mastodon_service }}" + mode: "0640" + +- name: Check if secrets need to be generated or not + ansible.builtin.shell: "grep -P SECRET_KEY_BASE=[[:alnum:]]{128} .env.production" + args: + chdir: "~/mastodon" + become_user: "{{ mastodon_service }}" + register: secrets + failed_when: "secrets.rc == 2" + +- block: + - name: Generate secret for SECRET_KEY_BASE + ansible.builtin.shell: '. ~/.profile && sed -i -r "s/SECRET_KEY_BASE=/SECRET_KEY_BASE=$(RAILS_ENV=production bundle exec rake secret)/" .env.production' + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Generate secret for OTP_SECRET + ansible.builtin.shell: '. ~/.profile && sed -i -r "s/OTP_SECRET=/OTP_SECRET=$(RAILS_ENV=production bundle exec rake secret)/" .env.production' + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Generate secret for VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY + ansible.builtin.shell: . ~/.profile && RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key > vapid.tmp | head -1 | cut -c 19- + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Read VAPID_PRIVATE_KEY secret from temp file + ansible.builtin.shell: "cat vapid.tmp | head -1 | cut -c 19-" + args: + chdir: "~/mastodon" + register: app_vapid_private_key + - name: Read VAPID_PUBLIC_KEY secret from temp file + ansible.builtin.shell: "cat vapid.tmp | tail -1 | cut -c 18-" + args: + chdir: "~/mastodon" + register: app_vapid_public_key + - name: Delete secrets temp file + ansible.builtin.file: + path: "~/mastodon/vapid.tmp" + state: absent + - name: Write app_vapid_private_key to production .env file + ansible.builtin.lineinfile: + path: "~/mastodon/.env.production" + regexp: '^VAPID_PRIVATE_KEY=' + line: "VAPID_PRIVATE_KEY={{ mastodon_app_vapid_private_key.stdout }}" + - name: Write app_vapid_public_key to production .env file + ansible.builtin.lineinfile: + path: "~/mastodon/.env.production" + regexp: '^VAPID_PUBLIC_KEY=' + line: "VAPID_PUBLIC_KEY={{ mastodon_app_vapid_public_key.stdout }}" + become_user: "{{ mastodon_service }}" + when: "secrets.rc == 1" + +- name: Check if mastodon database is already present or not + ansible.builtin.shell: | + psql -lqt | cut -d \| -f 1 | grep -qw {{ mastodon_service }}_production + become_user: postgres + register: db_present + failed_when: "db_present.rc == 2" + +- name: Setup database schema if database not already present + ansible.builtin.shell: ". ~/.profile && RAILS_ENV=production SAFETY_ASSURED=1 bundle exec rails db:setup" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + become_user: "{{ mastodon_service }}" + when: "db_present.rc == 1" + +- name: Precompile assets + ansible.builtin.shell: ". ~/.profile && RAILS_ENV=production bundle exec rails assets:precompile" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + become_user: "{{ mastodon_service }}" + +- name: Adjust permissions of files in public folder + ansible.builtin.file: + path: "~/mastodon/public" + state: directory + mode: 'o=rX' + recurse: true + become_user: "{{ mastodon_service }}" + +- name: Add systemd target + ansible.builtin.template: + src: "mastodon.target.j2" + dest: "/etc/systemd/system/{{ mastodon_service }}.target" + +- name: Add systemd web unit + ansible.builtin.template: + src: "mastodon-web.service.j2" + dest: "/etc/systemd/system/{{ mastodon_service }}-web.service" + +- name: Add systemd sidekiq unit + ansible.builtin.template: + src: "mastodon-sidekiq.service.j2" + dest: "/etc/systemd/system/{{ mastodon_service }}-sidekiq.service" + +- name: Add systemd streaming unit + ansible.builtin.template: + src: "mastodon-streaming.service.j2" + dest: "/etc/systemd/system/{{ mastodon_service }}-streaming.service" + +- name: Enable systemd units + ansible.builtin.systemd: + name: "{{ item }}" + enabled: yes + daemon_reload: yes + loop: + - "{{ mastodon_service }}.target" + - "{{ mastodon_service }}-web.service" + - "{{ mastodon_service }}-sidekiq.service" + - "{{ mastodon_service }}-streaming.service" + +- name: Start services + ansible.builtin.service: + name: "{{ mastodon_service }}.target" + state: started + +- name: Check if SSL certificate is present and register result + ansible.builtin.stat: + path: "/etc/letsencrypt/live/{{ mastodon_domains |first }}/fullchain.pem" + register: ssl + +- name: Generate certificate only if required (first time) + block: + - name: Template vhost without SSL for successfull LE challengce + ansible.builtin.template: + src: "vhost.j2" + dest: "/etc/nginx/sites-available/{{ mastodon_service }}" + - name: Enable temporary nginx vhost for LE + ansible.builtin.file: + src: "/etc/nginx/sites-available/{{ mastodon_service }}" + dest: "/etc/nginx/sites-enabled/{{ mastodon_service }}" + state: link + - name: Reload nginx conf + ansible.builtin.service: + name: nginx + state: reloaded + - name: Generate certificate with certbot + ansible.builtin.shell: certbot certonly --webroot --webroot-path /var/lib/letsencrypt -d {{ mastodon_domains |first }} + when: ssl.stat.exists == false + +- name: (Re)check if SSL certificate is present and register result + ansible.builtin.stat: + path: "/etc/letsencrypt/live/{{ mastodon_domains |first }}/fullchain.pem" + register: ssl + +- name: (Re)template conf file for nginx vhost with SSL + ansible.builtin.template: + src: "vhost.j2" + dest: "/etc/nginx/sites-available/{{ mastodon_service }}" + +- name: Enable nginx vhost for mastodon + ansible.builtin.file: + src: "/etc/nginx/sites-available/{{ mastodon_service }}" + dest: "/etc/nginx/sites-enabled/{{ mastodon_service }}" + state: link + +- name: Reload nginx conf + ansible.builtin.service: + name: nginx + state: reloaded diff --git a/webapps/mastodon/tasks/upgrade.yml b/webapps/mastodon/tasks/upgrade.yml new file mode 100644 index 00000000..97e4f42b --- /dev/null +++ b/webapps/mastodon/tasks/upgrade.yml @@ -0,0 +1,98 @@ +--- +# tasks file for mastodon upgrade + +- name: Dump database to a file with compression + community.postgresql.postgresql_db: + name: "{{ mastodon_service }}_production" + state: dump + target: "~/{{ mastodon_service }}_production.sql.gz" + become_user: postgres + +- name: Install Ruby for service user (rbenv) + include_role: + name: rbenv + vars: + - username: "{{ mastodon_service }}" + - rbenv_ruby_version: "{{ mastodon_ruby_version }}" + +- name: Checkout (git) + ansible.builtin.git: + repo: "{{ mastodon_git_url }}" + dest: "~/mastodon/" + version: "{{ mastodon_git_version | default(omit) }}" + force: yes + update: yes + become_user: "{{ mastodon_service }}" + +- block: + - name: Install bundler + ansible.builtin.shell: ". ~/.profile && gem install bundler --no-document" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Install gem dependencies + ansible.builtin.shell: ". ~/.profile && bundle install --deployment --without development test -j{{ ansible_processor_count }}" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Install javascript dependencies + ansible.builtin.shell: ". ~/.profile && yarn install --pure-lockfile" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Upgrade browsers list db + ansible.builtin.shell: ". ~/.profile && npx update-browserslist-db@latest" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Run database migrations, skipping post-deployment + ansible.builtin.shell: ". ~/.profile && SKIP_POST_DEPLOYMENT_MIGRATIONS=true RAILS_ENV=production bundle exec rails db:migrate" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Precompile assets + ansible.builtin.shell: ". ~/.profile && RAILS_ENV=production bundle exec rails assets:precompile" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + - name: Adjust permissions of files in public folder + ansible.builtin.file: + path: "~/mastodon/public" + state: directory + mode: 'o=rX' + recurse: true + become_user: "{{ mastodon_service }}" + +- name: Restart services + ansible.builtin.service: + name: "{{ mastodon_service }}.target" + state: restarted + +- name: Run database migrations, this time including post-deployment + ansible.builtin.shell: ". ~/.profile && RAILS_ENV=production bundle exec rails db:migrate" + args: + chdir: "~/mastodon" + executable: /bin/bash # fails with /bin/sh + become_user: "{{ mastodon_service }}" + +- name: Restart services + ansible.builtin.service: + name: "{{ mastodon_service }}.target" + state: restarted + +- name: Define variable to skip next task by default + ansible.builtin.set_fact: + keep_db_dump: true + +- name: Remove database dump + ansible.builtin.file: + path: "~/{{ mastodon_service }}_production.sql.gz" + state: absent + become_user: postgres + when: keep_db_dump is undefined + tags: clean + +- name: Reload nginx conf + ansible.builtin.service: + name: nginx + state: reloaded diff --git a/webapps/mastodon/templates/env.j2 b/webapps/mastodon/templates/env.j2 new file mode 100644 index 00000000..0b6aec11 --- /dev/null +++ b/webapps/mastodon/templates/env.j2 @@ -0,0 +1,235 @@ +# Service dependencies +# You may set REDIS_URL instead for more advanced options +# You may also set REDIS_NAMESPACE to share Redis between multiple Mastodon servers +#REDIS_URL=unix:///var/run/redis/redis.sock +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= +REDIS_NAMESPACE={{ mastodon_service }} +# You may set DATABASE_URL instead for more advanced options +DB_HOST={{ mastodon_db_host }} +DB_USER={{ mastodon_db_user }} +DB_NAME={{ mastodon_db_name }} +DB_PASS='{{ mastodon_db_password }}' +DB_PORT=5432 +# Optional ElasticSearch configuration +#ES_ENABLED={{ mastodon_app_es_enabled | default('false') }} +#ES_HOST={{ mastodon_app_es_host | default('localhost') }} +#ES_PORT={{ mastodon_app_es_port | default('9200') }} + +# Federation +# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation. +# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com. +LOCAL_DOMAIN={{ mastodon_domains |first }} + +# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links) + +# Use this only if you need to run mastodon on a different domain than the one used for federation. +# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md +# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING. +# WEB_DOMAIN=mastodon.example.com + +# Use this if you want to have several aliases handler@example1.com +# handler@example2.com etc. for the same user. LOCAL_DOMAIN should not +# be added. Comma separated values +# ALTERNATE_DOMAINS=example1.com,example2.com + +# Application secrets +# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose) +SECRET_KEY_BASE={{ mastodon_app_secret_key_base }} +OTP_SECRET={{ mastodon_app_otp_secret }} + +# VAPID keys (used for push notifications +# You can generate the keys using the following command (first is the private key, second is the public one) +# You should only generate this once per instance. If you later decide to change it, all push subscription will +# be invalidated, requiring the users to access the website again to resubscribe. +# +# Generate with `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rake mastodon:webpush:generate_vapid_key` if you use docker compose) +# +# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html +VAPID_PRIVATE_KEY={{ mastodon_app_vapid_private_key }} +VAPID_PUBLIC_KEY={{ mastodon_app_vapid_public_key }} + +# Registrations +# Single user mode will disable registrations and redirect frontpage to the first profile +# SINGLE_USER_MODE=true +# Prevent registrations with following e-mail domains +# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc +# Only allow registrations with the following e-mail domains +# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc + +# Optionally change default language +# DEFAULT_LOCALE=de + +# E-mail configuration +# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers +# If you want to use an SMTP server without authentication (e.g local Postfix relay) +# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and +# *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough). +SMTP_SERVER={{ mastodon_app_smtp_server | default('smtp.mailgun.org') }} +SMTP_PORT={{ mastodon_app_smtp_port | default('587') }} +#SMTP_LOGIN= +#SMTP_PASSWORD= +SMTP_FROM_ADDRESS={{ mastodon_app_smtp_from_address | default('notifications@example.com') }} +#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN +#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail +SMTP_AUTH_METHOD={{ mastodon_app_smtp_auth_method | default('plain') }} +#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt +#SMTP_OPENSSL_VERIFY_MODE={{ mastodon_app_smtp_openssl_verify_mode | default('peer') }} +#SMTP_ENABLE_STARTTLS_AUTO=true +#SMTP_TLS=true + +# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files. +# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system +# PAPERCLIP_ROOT_URL=/system + +# Optional asset host for multi-server setups +# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN +# if WEB_DOMAIN is not set. For example, the server may have the +# following header field: +# Access-Control-Allow-Origin: https://example.com/ +# CDN_HOST=https://assets.example.com + +# S3 (optional) +# The attachment host must allow cross origin request from WEB_DOMAIN or +# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the +# following header field: +# Access-Control-Allow-Origin: https://192.168.1.123:9000/ +# S3_ENABLED=true +# S3_BUCKET= +# AWS_ACCESS_KEY_ID= +# AWS_SECRET_ACCESS_KEY= +# S3_REGION= +# S3_PROTOCOL=http +# S3_HOSTNAME=192.168.1.123:9000 + +# S3 (Minio Config (optional) Please check Minio instance for details) +# The attachment host must allow cross origin request - see the description +# above. +# S3_ENABLED=true +# S3_BUCKET= +# AWS_ACCESS_KEY_ID= +# AWS_SECRET_ACCESS_KEY= +# S3_REGION= +# S3_PROTOCOL=https +# S3_HOSTNAME= +# S3_ENDPOINT= +# S3_SIGNATURE_VERSION= + +# Swift (optional) +# The attachment host must allow cross origin request - see the description +# above. +# SWIFT_ENABLED=true +# SWIFT_USERNAME= +# For Keystone V3, the value for SWIFT_TENANT should be the project name +# SWIFT_TENANT= +# SWIFT_PASSWORD= +# Some OpenStack V3 providers require PROJECT_ID (optional) +# SWIFT_PROJECT_ID= +# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid +# issues with token rate-limiting during high load. +# SWIFT_AUTH_URL= +# SWIFT_CONTAINER= +# SWIFT_OBJECT_URL= +# SWIFT_REGION= +# Defaults to 'default' +# SWIFT_DOMAIN_NAME= +# Defaults to 60 seconds. Set to 0 to disable +# SWIFT_CACHE_TTL= + +# Optional alias for S3 (e.g. to serve files on a custom domain, possibly using Cloudfront or Cloudflare) +# S3_ALIAS_HOST= + +# Streaming API integration +# STREAMING_API_BASE_URL= + +# Advanced settings +# If you need to use pgBouncer, you need to disable prepared statements: +# PREPARED_STATEMENTS=false + +# Cluster number setting for streaming API server. +# If you comment out following line, cluster number will be `numOfCpuCores - 1`. +STREAMING_CLUSTER_NUM=1 + +# Docker mastodon user +# If you use Docker, you may want to assign UID/GID manually. +# UID=1000 +# GID=1000 + +# LDAP authentication (optional) +# LDAP_ENABLED=true +# LDAP_HOST=localhost +# LDAP_PORT=389 +# LDAP_METHOD=simple_tls +# LDAP_BASE= +# LDAP_BIND_DN= +# LDAP_PASSWORD= +# LDAP_UID=cn +# LDAP_SEARCH_FILTER="%{uid}=%{email}" + +# PAM authentication (optional) +# PAM authentication uses for the email generation the "email" pam variable +# and optional as fallback PAM_DEFAULT_SUFFIX +# The pam environment variable "email" is provided by: +# https://github.com/devkral/pam_email_extractor +# PAM_ENABLED=true +# Fallback email domain for email address generation (LOCAL_DOMAIN by default) +# PAM_EMAIL_DOMAIN=example.com +# Name of the pam service (pam "auth" section is evaluated) +# PAM_DEFAULT_SERVICE=rpam +# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default) +# PAM_CONTROLLED_SERVICE=rpam + +# Global OAuth settings (optional) : +# If you have only one strategy, you may want to enable this +# OAUTH_REDIRECT_AT_SIGN_IN=true + +# Optional CAS authentication (cf. omniauth-cas) : +# CAS_ENABLED=true +# CAS_URL=https://sso.myserver.com/ +# CAS_HOST=sso.myserver.com/ +# CAS_PORT=443 +# CAS_SSL=true +# CAS_VALIDATE_URL= +# CAS_CALLBACK_URL= +# CAS_LOGOUT_URL= +# CAS_LOGIN_URL= +# CAS_UID_FIELD='user' +# CAS_CA_PATH= +# CAS_DISABLE_SSL_VERIFICATION=false +# CAS_UID_KEY='user' +# CAS_NAME_KEY='name' +# CAS_EMAIL_KEY='email' +# CAS_NICKNAME_KEY='nickname' +# CAS_FIRST_NAME_KEY='firstname' +# CAS_LAST_NAME_KEY='lastname' +# CAS_LOCATION_KEY='location' +# CAS_IMAGE_KEY='image' +# CAS_PHONE_KEY='phone' + +# Optional SAML authentication (cf. omniauth-saml) +# SAML_ENABLED=true +# SAML_ACS_URL= +# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback +# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO +# SAML_IDP_CERT= +# SAML_IDP_CERT_FINGERPRINT= +# SAML_NAME_IDENTIFIER_FORMAT= +# SAML_CERT= +# SAML_PRIVATE_KEY= +# SAML_SECURITY_WANT_ASSERTION_SIGNED=true +# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true +# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true +# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1" +# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" +# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241" +# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42" +# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4" +# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1" +# SAML_ATTRIBUTES_STATEMENTS_VERIFIED= +# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL= + +# Use HTTP proxy for outgoing request (optional) +# http_proxy=http://gateway.local:8118 +# Access control for hidden service. +# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true diff --git a/webapps/mastodon/templates/mastodon-sidekiq.service.j2 b/webapps/mastodon/templates/mastodon-sidekiq.service.j2 new file mode 100644 index 00000000..62059bc4 --- /dev/null +++ b/webapps/mastodon/templates/mastodon-sidekiq.service.j2 @@ -0,0 +1,23 @@ +[Unit] +Description=mastodon-sidekiq +After=network.target +PartOf={{ mastodon_service }}.target + +[Service] +Type=simple +User={{ mastodon_service }} +Group={{ mastodon_service }} +UMask=0027 +WorkingDirectory=/home/{{ mastodon_service }}/mastodon/ +Environment="RAILS_ENV=production" +Environment="RAILS_LOG_LEVEL=error" +Environment="DB_POOL=25" +Environment="MALLOC_ARENA_MAX=2" +Environment="LD_PRELOAD=libjemalloc.so" +ExecStart=/home/{{ mastodon_service }}/.rbenv/shims/bundle exec sidekiq -c 25 +TimeoutSec=15 +Restart=always +SyslogIdentifier=%p + +[Install] +WantedBy={{ mastodon_service }}.target diff --git a/webapps/mastodon/templates/mastodon-streaming.service.j2 b/webapps/mastodon/templates/mastodon-streaming.service.j2 new file mode 100644 index 00000000..bf5bbb36 --- /dev/null +++ b/webapps/mastodon/templates/mastodon-streaming.service.j2 @@ -0,0 +1,21 @@ +[Unit] +Description=mastodon-streaming +After=network.target +PartOf={{ mastodon_service }}.target + +[Service] +Type=simple +User={{ mastodon_service }} +Group={{ mastodon_service }} +UMask=0027 +WorkingDirectory=/home/{{ mastodon_service }}/mastodon/ +Environment="NODE_ENV=production" +Environment="PORT={{ mastodon_node_port | default('4000') }}" +Environment="STREAMING_CLUSTER_NUM=1" +ExecStart=/bin/bash -lc "node ./streaming" +TimeoutSec=15 +Restart=always +SyslogIdentifier=%p + +[Install] +WantedBy={{ mastodon_service }}.target diff --git a/webapps/mastodon/templates/mastodon-web.service.j2 b/webapps/mastodon/templates/mastodon-web.service.j2 new file mode 100644 index 00000000..ba936851 --- /dev/null +++ b/webapps/mastodon/templates/mastodon-web.service.j2 @@ -0,0 +1,23 @@ +[Unit] +Description=mastodon-web +After=network.target +PartOf={{ mastodon_service }}.target + +[Service] +Type=simple +User={{ mastodon_service }} +Group={{ mastodon_service }} +UMask=0027 +WorkingDirectory=/home/{{ mastodon_service }}/mastodon/ +Environment="RAILS_ENV=production" +Environment="PORT={{ mastodon_puma_port | default('3000') }}" +Environment="RAILS_LOG_LEVEL=warn" +Environment="LD_PRELOAD=libjemalloc.so" +ExecStart=/home/{{ mastodon_service }}/.rbenv/shims/bundle exec puma -C config/puma.rb +ExecReload=/bin/kill -SIGUSR1 $MAINPID +TimeoutSec=15 +Restart=always +SyslogIdentifier=%p + +[Install] +WantedBy={{ mastodon_service }}.target diff --git a/webapps/mastodon/templates/mastodon.target.j2 b/webapps/mastodon/templates/mastodon.target.j2 new file mode 100644 index 00000000..858c3b4d --- /dev/null +++ b/webapps/mastodon/templates/mastodon.target.j2 @@ -0,0 +1,8 @@ +[Unit] +Description=Mastodon Microblogging service +Wants=redis-server.service +After=redis-server.service +Requires={{ mastodon_service }}-web.service {{ mastodon_service }}-sidekiq.service {{ mastodon_service }}-streaming.service + +[Install] +WantedBy=multi-user.target diff --git a/webapps/mastodon/templates/vhost.j2 b/webapps/mastodon/templates/vhost.j2 new file mode 100644 index 00000000..6639f5b7 --- /dev/null +++ b/webapps/mastodon/templates/vhost.j2 @@ -0,0 +1,96 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 80; + listen [::]:80; + server_name {{ mastodon_domains |first }}; + include /etc/nginx/snippets/letsencrypt.conf; + {% if ssl.stat.exists %} + location / { return 301 https://$host$request_uri; } + {% endif %} +} + +{% if ssl.stat.exists %} +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name {{ mastodon_domains |first }}; + + include /etc/nginx/snippets/letsencrypt.conf; + ssl_certificate /etc/letsencrypt/live/{{ mastodon_domains |first }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ mastodon_domains |first }}/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/{{ mastodon_domains |first }}/chain.pem; + + # OCSP stapling + ssl_stapling on; + ssl_stapling_verify on; + + + keepalive_timeout 70; + sendfile on; + client_max_body_size 0; + + root /home/{{ mastodon_service }}/mastodon/public; + + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + #add_header Strict-Transport-Security "max-age=31536000"; + #add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://{{ mastodon_domains |first }}; upgrade-insecure-requests"; + + location / { + try_files $uri @proxy; + } + + location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri @proxy; + } + + location @proxy { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Proxy ""; + proxy_pass_header Server; + + proxy_pass http://127.0.0.1:{{ mastodon_puma_port | default('3000') }}; + proxy_buffering off; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + tcp_nodelay on; + } + + location /api/v1/streaming { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Proxy ""; + + proxy_pass http://127.0.0.1:{{ mastodon_node_port | default('4000') }}; + proxy_buffering off; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + tcp_nodelay on; + } + + error_page 500 501 502 503 504 /500.html; +} +{% endif %} diff --git a/webapps/mastodon/tests/inventory b/webapps/mastodon/tests/inventory new file mode 100644 index 00000000..878877b0 --- /dev/null +++ b/webapps/mastodon/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/webapps/mastodon/tests/test.yml b/webapps/mastodon/tests/test.yml new file mode 100644 index 00000000..708aca84 --- /dev/null +++ b/webapps/mastodon/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - mastodon diff --git a/webapps/mastodon/vars/main.yml b/webapps/mastodon/vars/main.yml new file mode 100644 index 00000000..85eebfd2 --- /dev/null +++ b/webapps/mastodon/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for mastodon