diff --git a/playbooks/mastodon.yml b/playbooks/mastodon.yml
index b83b935..34fb948 100644
--- a/playbooks/mastodon.yml
+++ b/playbooks/mastodon.yml
@@ -1,25 +1,17 @@
----
-# Infrastructure
-# Ansible instructions to deploy the infrastructure
-# Copyright (C) 2019-2020 Christoph (Sheogorath) Kern
-# Copyright (C) 2020 Saibotk
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, version 3 of the License.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+- name: Install Mastodon.
-- name: Install & configure Mastodon
hosts: mastodon
+
roles:
- - docker
- - docker_cleanup
- - traefik
- - mastodon
+ - role: podman
+ become: true
+ tags:
+ - always
+ - podman
+ - role: caddy
+ become: true
+ tags:
+ - always
+ - caddy
+ - role: mastodon
+ become: true
diff --git a/roles/mastodon/README.md b/roles/mastodon/README.md
deleted file mode 100644
index e9562d3..0000000
--- a/roles/mastodon/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-Mastodon
-=========
-
-This will setup a Mastodon instance using their official docker container and traefik as a reverse proxy.
-
-**Note: This role will also care about migrations etc, when upgrading to a new version!**
-
-Requirements
-------------
-
-You will need to have docker, docker-compose and traefik installed or declared as dependencies with their respective roles.
-
-**This role assumes that you have setup traefik with an endpoint called `websecure`.**
-
-Role Variables
---------------
-
-**Please look at the [defaults/main.yml](defaults/main.yml) for all available variables and their description.**
-
-**Note: Lines that are commented out via `#` are usually still valid/used variables, but they are not defined by default, so they might enable a feature, when uncommenting/defining them!**
-
-### Global variables, that are used:
-
-- `proxy_network`: Defined by the local traefik installation, this is the shared proxy network used by traefik to reach the containers. (optional)
-- `proxy_hiddenservice`: Defined by the local traefik installation, this is used to generate the alt-svc header for the alternative Tor domain. (optional)
-
-Dependencies
-------------
-
-- docker
-- docker-compose
-- traefik
-
-License
--------
-
-GPL-3.0-only
diff --git a/roles/mastodon/defaults/main.yml b/roles/mastodon/defaults/main.yml
index 842fc72..fbdb22b 100644
--- a/roles/mastodon/defaults/main.yml
+++ b/roles/mastodon/defaults/main.yml
@@ -1,39 +1,10 @@
----
-# Default variables for the mastodon role
-
-# Infrastructure
-# Ansible instructions to deploy the infrastructure
-# Copyright (C) 2019-2020 Christoph (Sheogorath) Kern
-# Copyright (C) 2020 Saibotk
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, version 3 of the License.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-# The install location
mastodon_install_location: /srv/mastodon
-# The container data volume mount locations
-mastodon_database_location: "{{ mastodon_install_location }}/postgres"
-mastodon_public_location: "{{ mastodon_install_location }}/public"
-mastodon_redis_location: "{{ mastodon_install_location }}/redis"
-mastodon_elastic_location: "{{ mastodon_install_location }}/elasticsearch"
-mastodon_nginx_location: "{{ mastodon_install_location }}/nginx"
-# The certresolver that is used by traefik for this domain
-mastodon_traefik_certresolver: letsencrypt_http
-
-# The domain that traefik will server mastodon under
mastodon_domain: mastodon.example.com
+mastodon_postgres_password: "{{ lookup('passwordstore', 'mastodon.example.com/database create=true') }}"
+
# The mastodon configuration (see mastodon documentation for a reference / the `templates/.env.production` file)
mastodon_config:
local_domain: "{{ mastodon_domain }}"
@@ -48,23 +19,66 @@ mastodon_config:
smtp_login: undef
smtp_password: undef
smtp_from_address: mastodon@example.com
- enable_elasticsearch: false
-# Should we automatically adjust the needed sysctl setting for vm.max_map_count?
-mastodon_elasticsearch_adjust_sysctl: true
-# Container versions
+mastodon_streaming_containerimage: ghcr.io/mastodon/mastodon-streaming
+mastodon_containerimage: ghcr.io/mastodon/mastodon
# renovate: depName=ghcr.io/mastodon/mastodon
-mastodon_version: 4.3.2
-# renovate: depName=docker.io/library/postgres
-mastodon_database_version: 15.8
-# renovate: depName=docker.io/library/redis
-mastodon_redis_version: 7.4
-# renovate: depName=docker.elastic.co/elasticsearch/elasticsearch
-mastodon_elasticsearch_version: 7.17.25
+mastodon_image_tag: "v4.3.3"
-# Container tag definitions
-mastodon_image_version: "v{{ mastodon_version }}"
-mastodon_database_image_version: "{{ mastodon_database_version }}-alpine"
-mastodon_redis_image_version: "{{ mastodon_redis_version }}-alpine"
-mastodon_elasticsearch_image_version: "{{ mastodon_elasticsearch_version }}"
+mastodon_nginx_containerimage: docker.io/nginxinc/nginx-unprivileged
+# renovate: depName=docker.io/nginxinc/nginx-unprivileged
+mastodon_nginx_image_tag: "1.27.3-alpine"
+
+mastodon_postgres_containerimage: docker.io/library/postgres
+# renovate: depName=docker.io/library/postgres
+mastodon_postgres_image_tag: "17.2-alpine"
+
+mastodon_redis_containerimage: docker.io/library/redis
+# renovate: depName=docker.io/library/redis
+mastodon_redis_image_tag: "7.4-alpine"
+
+mastodon_elasticsearch_containerimage: docker.io/library/elasticsearch
+# renovate: depName=docker.io/library/elasticsearch
+mastodon_elasticsearch_image_tag: "7.17.26"
+
+
+mastodon_selinux_level: "{{ omit }}"
+mastodon_nginx_selinux_level: "{{ omit }}"
+mastodon_postgres_selinux_level: "{{ omit }}"
+mastodon_redis_selinux_level: "{{ omit }}"
+mastodon_elasticsearch_selinux_level: "{{ omit }}"
+## This will be used for the `mastodon_public_location` which will need to
+## be accessed by the main mastodon containers and by nginx.
+## Make sure it is accessible/compatible with `mastodon_selinux_level`
+## and `mastodon_nginx_selinux_level`.
+mastodon_public_selinux_level: "{{ omit }}"
+
+
+mastodon_memory_low: 512m
+mastodon_memory_high: 0
+mastodon_swap_max: -1
+
+mastodon_sidekiq_memory_low: 512m
+mastodon_sidekiq_memory_high: 0
+mastodon_sidekiq_swap_max: -1
+
+mastodon_streaming_memory_low: 64m
+mastodon_streaming_memory_high: 256m
+mastodon_streaming_swap_max: -1
+
+mastodon_nginx_memory_low: 32m
+mastodon_nginx_memory_high: 64m
+mastodon_nginx_swap_max: -1
+
+mastodon_redis_memory_low: 128m
+mastodon_redis_memory_high: 512m
+mastodon_redis_swap_max: -1
+
+mastodon_elasticsearch_low: 1024m
+mastodon_elasticsearch_memory_high: 1536m
+mastodon_elasticsearch_swap_max: -1
+
+mastodon_postgres_memory_low: 512m
+mastodon_postgres_memory_high: 0
+mastodon_postgres_swap_max: -1
diff --git a/roles/mastodon/handlers/main.yml b/roles/mastodon/handlers/main.yml
index 101e012..ccc3830 100644
--- a/roles/mastodon/handlers/main.yml
+++ b/roles/mastodon/handlers/main.yml
@@ -1,14 +1,84 @@
----
-# Handlers file for the mastodon role
-
-- name: Pull mastodon image
- community.docker.docker_image:
- name: "docker.io/tootsuite/mastodon:{{ mastodon_image_version }}"
- source: pull
+- name: Apply new SELinux file context to filesystem.
+ ansible.builtin.command: "restorecon -irF {{ mastodon_install_location }}"
become: true
+ listen: "mastodon selinux context changed"
+ changed_when: false
-- name: Stop mastodon for upgrade
- community.docker.docker_compose_v2:
- state: stopped
- project_src: "{{ mastodon_install_location }}"
+- name: Restart mastodon web service.
+ ansible.builtin.systemd:
+ state: restarted
+ name: mastodon-web.service
+ daemon_reload: true
become: true
+ listen:
+ - "mastodon web service changed"
+ - "mastodon selinux context changed"
+ - "mastodon postgres socket changed"
+ - "mastodon redis socket changed"
+ - "mastodon env changed"
+
+- name: Restart mastodon streaming service.
+ ansible.builtin.systemd:
+ state: restarted
+ name: mastodon-streaming.service
+ daemon_reload: true
+ become: true
+ listen:
+ - "mastodon streaming service changed"
+ - "mastodon selinux context changed"
+ - "mastodon postgres socket changed"
+ - "mastodon redis socket changed"
+ - "mastodon env changed"
+
+- name: Restart mastodon sidekiq service.
+ ansible.builtin.systemd:
+ state: restarted
+ name: mastodon-sidekiq.service
+ daemon_reload: true
+ become: true
+ listen:
+ - "mastodon sidekiq service changed"
+ - "mastodon selinux context changed"
+ - "mastodon postgres socket changed"
+ - "mastodon redis socket changed"
+ - "mastodon env changed"
+
+- name: Restart mastodon postgres service.
+ ansible.builtin.systemd:
+ state: restarted
+ name: mastodon-postgres.service
+ daemon_reload: true
+ become: true
+ listen:
+ - "mastodon postgres service changed"
+ - "mastodon selinux context changed"
+
+- name: Restart mastodon redis service.
+ ansible.builtin.systemd:
+ state: restarted
+ name: mastodon-redis.service
+ daemon_reload: true
+ become: true
+ listen:
+ - "mastodon redis service changed"
+ - "mastodon selinux context changed"
+
+- name: Restart mastodon nginx service.
+ ansible.builtin.systemd:
+ state: restarted
+ name: mastodon-nginx.service
+ daemon_reload: true
+ become: true
+ listen:
+ - "mastodon nginx service changed"
+ - "mastodon selinux context changed"
+
+- name: Restart mastodon elasticsearch service.
+ ansible.builtin.systemd:
+ state: restarted
+ name: mastodon-elasticsearch.service
+ daemon_reload: true
+ become: true
+ listen:
+ - "mastodon elasticsearch service changed"
+ - "mastodon selinux context changed"
diff --git a/roles/mastodon/meta/main.yml b/roles/mastodon/meta/main.yml
index 8dd1ae8..c43d2cd 100644
--- a/roles/mastodon/meta/main.yml
+++ b/roles/mastodon/meta/main.yml
@@ -1,44 +1,20 @@
galaxy_info:
author: saibotk
- description: "Deploys a mastodon server via docker using traefik."
+ description: Deploy mastodon with podman and systemd.
+
+ issue_tracker_url: https://git.saibotk.de/saibotk.de/infrastructure/-/issues
+
license: GPL-3.0-only
- min_ansible_version: "2.9"
- standalone: true
+
+ min_ansible_version: "2.10"
platforms:
- - name: EL
- versions:
- - all
- - name: GenericUNIX
- versions:
- - all
- name: Fedora
versions:
- - all
- - name: opensuse
- versions:
- - all
- - name: GenericBSD
- versions:
- - all
- - name: FreeBSD
- versions:
- - all
- - name: Ubuntu
- versions:
- - all
- - name: SLES
- versions:
- - all
- - name: GenericLinux
- versions:
- - all
- - name: Debian
- versions:
- - all
+ - "41"
+
+ standalone: true
galaxy_tags: []
-dependencies:
- - role: docker
- - role: traefik
+dependencies: []
diff --git a/roles/mastodon/tasks/main.yml b/roles/mastodon/tasks/main.yml
index 03eaee6..04a3d34 100644
--- a/roles/mastodon/tasks/main.yml
+++ b/roles/mastodon/tasks/main.yml
@@ -1,172 +1,309 @@
----
-# Tasks file for the mastodon role
-
-# Infrastructure
-# Ansible instructions to deploy the infrastructure
-# Copyright (C) 2019-2020 Christoph (Sheogorath) Kern
-# Copyright (C) 2020 Alexander Wellbrock
-# Copyright (C) 2020 Saibotk
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, version 3 of the License.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
- name: Update default SELinux contexts
community.general.sefcontext:
- target: "{{ item }}(/.*)?"
+ target: "{{ item.target }}"
setype: "container_file_t"
+ selevel: "{{ item.selevel }}"
state: present
- with_items:
- - "{{ mastodon_database_location }}"
- - "{{ mastodon_public_location }}"
- - "{{ mastodon_redis_location }}"
- - "{{ mastodon_elastic_location }}"
- - "{{ mastodon_nginx_location }}"
+ loop:
+ - target: "{{ mastodon_public_location }}(/.*)?"
+ selevel: "{{ mastodon_public_selinux_level }}"
+ - target: "{{ mastodon_postgres_location }}(/.*)?"
+ selevel: "{{ mastodon_postgres_selinux_level }}"
+ - target: "{{ mastodon_redis_location }}(/.*)?"
+ selevel: "{{ mastodon_redis_selinux_level }}"
+ - target: "{{ mastodon_install_location }}/redis.conf"
+ selevel: "{{ mastodon_redis_selinux_level }}"
+ - target: "{{ mastodon_elasticsearch_location }}(/.*)?"
+ selevel: "{{ mastodon_elasticsearch_selinux_level }}"
+ - target: "{{ mastodon_install_location }}/default.conf"
+ selevel: "{{ mastodon_nginx_selinux_level }}"
become: true
+ notify: "mastodon selinux context changed"
-- name: Create install directory
+- name: Create mastodon directories.
ansible.builtin.file:
- path: "{{ item }}"
- state: directory
- mode: "0700"
+ path: "{{ mastodon_install_location }}"
owner: "root"
group: "root"
- with_items:
- - "{{ mastodon_install_location }}"
+ mode: "0700"
+ state: directory
become: true
-- name: Create data directories
- ansible.builtin.file: # noqa risky-file-permissions # Container manages permissions on its own
- path: "{{ item }}"
- state: directory
- setype: "container_file_t"
- with_items:
- - "{{ mastodon_database_location }}"
- - "{{ mastodon_public_location }}"
- - "{{ mastodon_redis_location }}"
- - "{{ mastodon_elastic_location }}"
- - "{{ mastodon_nginx_location }}"
- become: true
+- name: Ensure mastodon data directories exist.
+ block:
+ - name: Stat mastodon public dir.
+ ansible.builtin.stat:
+ path: "{{ mastodon_public_location }}"
+ become: true
+ register: mastodon_stat_public_dir
-- name: Adjust elasticsearch directory permissions
- ansible.builtin.file:
- path: "{{ mastodon_elastic_location }}"
- state: directory
- setype: "container_file_t"
- mode: "0750"
- owner: 1000
- group: "root"
- become: true
+ - name: Stat mastodon postgres dir.
+ ansible.builtin.stat:
+ path: "{{ mastodon_postgres_location }}"
+ become: true
+ register: mastodon_stat_postgres_dir
+
+ - name: Stat mastodon redis dir.
+ ansible.builtin.stat:
+ path: "{{ mastodon_redis_location }}"
+ become: true
+ register: mastodon_stat_redis_dir
+
+ - name: Stat mastodon elasticsearch dir.
+ ansible.builtin.stat:
+ path: "{{ mastodon_elasticsearch_location }}"
+ become: true
+ register: mastodon_stat_elasticsearch_dir
+
+ - name: Create mastodon data directories.
+ ansible.builtin.file:
+ path: "{{ item.path }}"
+ state: directory
+ mode: "0700"
+ owner: "{{ item.owner }}"
+ group: "{{ item.group }}"
+ loop:
+ - path: "{{ mastodon_public_location }}"
+ owner: "{{ mastodon_stat_public_dir.stat.uid | default('root') }}"
+ group: "{{ mastodon_stat_public_dir.stat.gid | default('root') }}"
+ - path: "{{ mastodon_postgres_location }}"
+ owner: "{{ mastodon_stat_postgres_dir.stat.uid | default('root') }}"
+ group: "{{ mastodon_stat_postgres_dir.stat.gid | default('root') }}"
+ - path: "{{ mastodon_redis_location }}"
+ owner: "{{ mastodon_stat_redis_dir.stat.uid | default('root') }}"
+ group: "{{ mastodon_stat_redis_dir.stat.gid | default('root') }}"
+ - path: "{{ mastodon_elasticsearch_location }}"
+ owner: "{{ mastodon_stat_elasticsearch_dir.stat.uid | default('root') }}"
+ group: "{{ mastodon_stat_elasticsearch_dir.stat.gid | default('root') }}"
+ become: true
- name: Adjust sysctl settings for elasticsearch
ansible.posix.sysctl:
name: vm.max_map_count
value: "262144"
state: present
- when:
- - mastodon_config.enable_elasticsearch is defined and mastodon_config.enable_elasticsearch
- - mastodon_elasticsearch_adjust_sysctl
become: true
-- name: Create public data directory
- ansible.builtin.file:
- path: "{{ mastodon_public_location }}/system"
- mode: "0755"
- owner: "991"
- group: "991"
- state: directory
- setype: "container_file_t"
- become: true
-
-- name: Deploy nginx proxy config file
+- name: Deploy redis config file.
ansible.builtin.template:
- src: "default.conf"
- dest: "{{ mastodon_nginx_location }}/default.conf"
+ src: redis.conf.j2
+ dest: "{{ mastodon_install_location }}/redis.conf"
+ owner: "{{ mastodon_stat_redis_dir.stat.uid | default('root') }}"
+ group: "{{ mastodon_stat_redis_dir.stat.gid | default('root') }}"
+ mode: "0600"
+ become: true
+ notify: "mastodon redis service changed"
+
+- name: Deploy mastodon environment file.
+ ansible.builtin.template:
+ src: mastodon.env.j2
+ dest: "{{ mastodon_install_location }}/mastodon.env"
+ mode: "0600"
+ owner: "root"
+ group: "root"
+ become: true
+ notify: "mastodon env changed"
+
+- name: Deploy postgres environment file.
+ ansible.builtin.template:
+ src: mastodon-postgres.env.j2
+ dest: "{{ mastodon_install_location }}/mastodon-postgres.env"
mode: "0600"
owner: "root"
group: "root"
become: true
-- name: Check if migration is needed
- ansible.builtin.command: "grep -q 'mastodon/mastodon:{{ mastodon_image_version }}' '{{ mastodon_install_location }}/docker-compose.yml'"
- register: mastodon_version_fact
- ignore_errors: true
- changed_when: mastodon_version_fact.rc > 0
- failed_when: false
- become: true
- notify: ["Pull mastodon image", "Stop mastodon for upgrade"]
-
-- name: Immediately run / flush Ansible handlers
- ansible.builtin.meta: "flush_handlers"
-
-- name: Deploy config
+- name: Deploy elasticsearch environment file.
ansible.builtin.template:
- src: ".env.production"
- dest: "{{ mastodon_install_location }}/.env.production"
+ src: mastodon-elasticsearch.env.j2
+ dest: "{{ mastodon_install_location }}/mastodon-elasticsearch.env"
mode: "0600"
owner: "root"
group: "root"
- tags:
- - mastodon
become: true
-- name: Deploy docker-compose.yml
+- name: Add caddy config file.
+ block:
+ - name: Check caddy config dir.
+ ansible.builtin.stat:
+ path: "{{ caddy_install_dir }}/config"
+ become: true
+ register: caddy_stat_config_dir
+
+ - name: Template caddy config for mastodon.
+ ansible.builtin.template:
+ src: mastodon.caddy.j2
+ dest: "{{ caddy_install_dir }}/config/mastodon.caddy"
+ mode: "0600"
+ setype: "container_file_t"
+ selevel: "{{ caddy_selinux_level }}"
+ owner: "{{ caddy_stat_config_dir.stat.uid }}"
+ group: "{{ caddy_stat_config_dir.stat.gid }}"
+ notify: "caddy config changed"
+ become: true
+
+- name: Ensure mastodon nginx config exists.
+ block:
+ - name: Stat mastodon nginx config.
+ ansible.builtin.stat:
+ path: "{{ mastodon_install_location }}/default.conf"
+ become: true
+ register: mastodon_stat_nginx_config
+
+ - name: Deploy mastodon nginx config.
+ ansible.builtin.template:
+ src: default.conf.j2
+ dest: "{{ mastodon_install_location }}/default.conf"
+ mode: "0600"
+ owner: "{{ mastodon_stat_nginx_config.stat.uid | default('root') }}"
+ group: "{{ mastodon_stat_nginx_config.stat.gid | default('root') }}"
+ setype: "container_file_t"
+ selevel: "{{ mastodon_nginx_selinux_level }}"
+ become: true
+ notify: "mastodon nginx service changed"
+
+- name: Create mastodon postgres socket volume.
ansible.builtin.template:
- src: "docker-compose.yml"
- dest: "{{ mastodon_install_location }}/docker-compose.yml"
- mode: "0600"
+ src: mastodon-postgres-socket.volume
+ dest: /etc/containers/systemd/mastodon-postgres-socket.volume
owner: "root"
group: "root"
- validate: docker compose -f %s config -q
- tags:
- - mastodon
+ mode: "0644"
+ become: true
+ notify:
+ - "mastodon postgres socket changed"
+
+- name: Create mastodon redis socket volume.
+ ansible.builtin.template:
+ src: mastodon-redis-socket.volume
+ dest: /etc/containers/systemd/mastodon-redis-socket.volume
+ owner: "root"
+ group: "root"
+ mode: "0644"
+ become: true
+ notify:
+ - "mastodon redis socket changed"
+
+- name: Create mastodon web container file.
+ ansible.builtin.template:
+ src: mastodon-web.container.j2
+ dest: /etc/containers/systemd/mastodon-web.container
+ owner: "root"
+ group: "root"
+ mode: "0644"
+ become: true
+ notify: "mastodon web service changed"
+
+- name: Create mastodon streaming container file.
+ ansible.builtin.template:
+ src: mastodon-streaming.container.j2
+ dest: /etc/containers/systemd/mastodon-streaming.container
+ owner: "root"
+ group: "root"
+ mode: "0644"
+ become: true
+ notify: "mastodon streaming service changed"
+
+- name: Create mastodon sidekiq container file.
+ ansible.builtin.template:
+ src: mastodon-sidekiq.container.j2
+ dest: /etc/containers/systemd/mastodon-sidekiq.container
+ owner: "root"
+ group: "root"
+ mode: "0644"
+ become: true
+ notify: "mastodon sidekiq service changed"
+
+- name: Create mastodon postgres container file.
+ ansible.builtin.template:
+ src: mastodon-postgres.container.j2
+ dest: /etc/containers/systemd/mastodon-postgres.container
+ owner: "root"
+ group: "root"
+ mode: "0644"
+ become: true
+ notify: "mastodon postgres service changed"
+
+- name: Create mastodon redis container file.
+ ansible.builtin.template:
+ src: mastodon-redis.container.j2
+ dest: /etc/containers/systemd/mastodon-redis.container
+ owner: "root"
+ group: "root"
+ mode: "0644"
+ become: true
+ notify: "mastodon redis service changed"
+
+- name: Create mastodon elasticsearch container file.
+ ansible.builtin.template:
+ src: mastodon-elasticsearch.container.j2
+ dest: /etc/containers/systemd/mastodon-elasticsearch.container
+ owner: "root"
+ group: "root"
+ mode: "0644"
+ become: true
+ notify: "mastodon elasticsearch service changed"
+
+- name: Create mastodon nginx container file.
+ ansible.builtin.template:
+ src: mastodon-nginx.container.j2
+ dest: /etc/containers/systemd/mastodon-nginx.container
+ owner: "root"
+ group: "root"
+ mode: "0644"
+ become: true
+ notify: "mastodon nginx service changed"
+
+# TODO: Quadlet does not change networks when their definition changes
+# We need to find a solution to recreate the network
+- name: Create mastodon frontend network definition file.
+ ansible.builtin.template:
+ src: mastodon-frontend.network.j2
+ dest: "/etc/containers/systemd/mastodon-frontend.network"
+ owner: "root"
+ group: "root"
+ mode: "0644"
become: true
-- name: Migrate database
- ansible.builtin.command: "docker-compose run --rm web rails db:migrate"
- args:
- chdir: "{{ mastodon_install_location }}"
- when:
- # noqa no-handler
- - mastodon_version_fact is changed
- tags:
- - docker
- - mastodon
+# TODO: Quadlet does not change networks when their definition changes
+# We need to find a solution to recreate the network
+- name: Create mastodon backend network definition file.
+ ansible.builtin.template:
+ src: mastodon-backend.network.j2
+ dest: "/etc/containers/systemd/mastodon-backend.network"
+ owner: "root"
+ group: "root"
+ mode: "0644"
become: true
- changed_when: true
- environment:
- PYTHONPATH: ""
-- name: Clear cache
- ansible.builtin.command: docker-compose run --rm web bin/tootctl cache clear
- args:
- chdir: "{{ mastodon_install_location }}"
- when:
- # noqa no-handler
- - mastodon_version_fact is changed
- tags:
- - docker
- - mastodon
+- name: Ensure mastodon services are enabled.
+ ansible.builtin.systemd:
+ enabled: true
+ name: "{{ item }}"
+ daemon_reload: true
+ loop:
+ - mastodon-postgres.service
+ - mastodon-redis.service
+ - mastodon-elasticsearch.service
+ - mastodon-web.service
+ - mastodon-streaming.service
+ - mastodon-sidekiq.service
+ - mastodon-nginx.service
become: true
- changed_when: true
- environment:
- PYTHONPATH: ""
-- name: Compose mastodon
- community.docker.docker_compose_v2:
- state: present
- project_src: "{{ mastodon_install_location }}"
- pull: always
- remove_orphans: true
- tags:
- - docker
- - mastodon
+- name: Flush handlers
+ ansible.builtin.meta: flush_handlers
+
+- name: Ensure mastodon services are started.
+ ansible.builtin.systemd:
+ state: started
+ name: "{{ item }}"
+ loop:
+ - mastodon-postgres.service
+ - mastodon-redis.service
+ - mastodon-elasticsearch.service
+ - mastodon-web.service
+ - mastodon-streaming.service
+ - mastodon-sidekiq.service
+ - mastodon-nginx.service
become: true
diff --git a/roles/mastodon/templates/.env.production b/roles/mastodon/templates/.env.production
deleted file mode 100644
index 9661187..0000000
--- a/roles/mastodon/templates/.env.production
+++ /dev/null
@@ -1,266 +0,0 @@
-{{ ansible_managed | comment }}
-# 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_HOST=redis
-REDIS_PORT=6379
-# You may set DATABASE_URL instead for more advanced options
-DB_HOST=db
-DB_USER=postgres
-DB_NAME=postgres
-DB_PASS=
-DB_PORT=5432
-
-{% if mastodon_config.enable_elasticsearch is defined %}
-# Optional ElasticSearch configuration
-# You may also set ES_PREFIX to share the same cluster between multiple Mastodon servers (falls back to REDIS_NAMESPACE if not set)
-ES_ENABLED={{ mastodon_config.enable_elasticsearch | bool | lower }}
-ES_HOST=es
-ES_PORT=9200
-{% endif %}
-
-# 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_config.local_domain }}
-
-# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
-
-{% if mastodon_config.web_domain is defined %}
-# 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_config.web_domain }}
-
-{% endif %}
-# 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 rails secret` task (`docker-compose run --rm web rails secret` if you use docker compose)
-SECRET_KEY_BASE={{ mastodon_config.secret_key_base }}
-OTP_SECRET={{ mastodon_config.otp_secret }}
-# DB encryption secrets
-# Generate them yourself, each 32 alphanumeric or
-# Generate them with `RAILS_ENV=production bundle exec rails db:encryption:init` task (`docker-compose run --rm web rails db:encryption:init` if you use docker compose)
-ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY={{ mastodon_config.ar_enc_deterministic_key }}
-ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT={{ mastodon_config.ar_enc_derivation_salt }}
-ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY={{ mastodon_config.ar_enc_primary_key }}
-
-# 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 rails mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rails 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_config.vapid_private_key }}
-VAPID_PUBLIC_KEY={{ mastodon_config.vapid_public_key }}
-
-# Registrations
-{% if mastodon_config.single_user_mode is defined %}
-# Single user mode will disable registrations and redirect frontpage to the first profile
-SINGLE_USER_MODE={{ mastodon_config.single_user_mode | bool | lower }}
-{% endif %}
-# 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_config.smtp_server }}
-SMTP_PORT={{ mastodon_config.smtp_port | default(587) }}
-SMTP_LOGIN={{ mastodon_config.smtp_login }}
-SMTP_PASSWORD={{ mastodon_config.smtp_password }}
-SMTP_FROM_ADDRESS={{ mastodon_config.smtp_from_address }}
-#SMTP_REPLY_TO=
-#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
-#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
-#SMTP_AUTH_METHOD=plain
-#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
-#SMTP_OPENSSL_VERIFY_MODE=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=
-
-# Google Cloud Storage (optional)
-# Use S3 compatible API. Since GCS does not support Multipart Upload,
-# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
-# The attachment host must allow cross origin request - see the description
-# above.
-# S3_ENABLED=true
-# AWS_ACCESS_KEY_ID=
-# AWS_SECRET_ACCESS_KEY=
-# S3_REGION=
-# S3_PROTOCOL=https
-# S3_HOSTNAME=storage.googleapis.com
-# S3_ENDPOINT=https://storage.googleapis.com
-# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes
-
-# 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.
-{% if mastodon_config.docker_uid is defined %}
-UID={{ mastodon_config.docker_uid }}
-{% endif %}
-{% if mastodon_config.docker_gid is defined %}
-GID={{ mastodon_config.docker_gid }}
-{% endif %}
-
-# 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=https://flausch.social/auth/auth/saml/callback
-#SAML_ISSUER=mastodon
-#SAML_IDP_SSO_TARGET_URL=https://auth.the-rainbow-unicorns.de/auth/realms/unicorns/protocol/saml/clients/mastodon
-#SAML_IDP_CERT=
-#SAML_IDP_CERT_FINGERPRINT=
-#SAML_NAME_IDENTIFIER_FORMAT=urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
-#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=name
-#SAML_ATTRIBUTES_STATEMENTS_EMAIL=email
-#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=name
-#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/roles/mastodon/templates/default.conf b/roles/mastodon/templates/default.conf.j2
similarity index 79%
rename from roles/mastodon/templates/default.conf
rename to roles/mastodon/templates/default.conf.j2
index e527452..1c4421e 100644
--- a/roles/mastodon/templates/default.conf
+++ b/roles/mastodon/templates/default.conf.j2
@@ -3,12 +3,29 @@ map $http_upgrade $connection_upgrade {
'' close;
}
+upstream backend {
+ server backend-mastodon-web:3000 fail_timeout=0;
+}
+
+upstream streaming {
+ # Instruct nginx to send connections to the server with the least number of connections
+ # to ensure load is distributed evenly.
+ least_conn;
+
+ server backend-mastodon-streaming:4000 fail_timeout=0;
+ # Uncomment these lines for load-balancing multiple instances of streaming for scaling,
+ # this assumes your running the streaming server on ports 4000, 4001, and 4002:
+ # server 127.0.0.1:4001 fail_timeout=0;
+ # server 127.0.0.1:4002 fail_timeout=0;
+}
+
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;
server {
- listen 80;
- listen [::]:80;
+ listen 8080;
+ listen [::]:8080;
server_name {{ mastodon_domain }};
+
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
@@ -29,6 +46,7 @@ server {
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 image/svg+xml image/x-icon;
+ gzip_static on;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
@@ -60,7 +78,7 @@ server {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Proxy "";
- proxy_pass http://streaming:4000;
+ proxy_pass http://streaming;
proxy_buffering off;
proxy_redirect off;
proxy_http_version 1.1;
@@ -78,7 +96,7 @@ server {
proxy_set_header Proxy "";
proxy_pass_header Server;
- proxy_pass http://web:3000;
+ proxy_pass http://backend;
proxy_buffering on;
proxy_redirect off;
proxy_http_version 1.1;
@@ -94,7 +112,5 @@ server {
tcp_nodelay on;
}
-
-
error_page 404 500 501 502 503 504 /500.html;
}
diff --git a/roles/mastodon/templates/docker-compose.yml b/roles/mastodon/templates/docker-compose.yml
deleted file mode 100644
index 4e362af..0000000
--- a/roles/mastodon/templates/docker-compose.yml
+++ /dev/null
@@ -1,179 +0,0 @@
-{{ ansible_managed | comment }}
-
-# Infrastructure
-# Ansible instructions to deploy the infrastructure
-# Copyright (C) 2019-2020 Christoph (Sheogorath) Kern
-# Copyright (C) 2020 Saibotk
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, version 3 of the License.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-version: '2.1'
-services:
- nginx:
- image: docker.io/library/nginx:alpine
- mem_limit: 32mb
- memswap_limit: 64mb
- labels:
- - "traefik.http.routers.mastodon.rule=Host(`{{ mastodon_domain }}`) && PathPrefix(`/`)"
- - "traefik.http.routers.mastodon.entrypoints=websecure"
- - "traefik.http.routers.mastodon.tls.certresolver={{ mastodon_traefik_certresolver }}"
- - "traefik.http.routers.mastodon.middlewares=mastodon,compress"
- - "traefik.http.middlewares.mastodon.headers.sslredirect=true"
- - "traefik.http.middlewares.mastodon.headers.stsSeconds=63072000"
- - "traefik.http.middlewares.mastodon.headers.referrerPolicy=same-origin"
-
- - "traefik.enable=true"
-{% if proxy_network is defined %}
- - "traefik.docker.network={{ proxy_network }}"
-{% endif %}
- networks:
- frontend:
-{% if proxy_network is defined %}
- {{ proxy_network }}:
-{% endif %}
- volumes:
- - "{{ mastodon_nginx_location }}/default.conf:/etc/nginx/conf.d/default.conf:ro"
- - "{{ mastodon_public_location }}:/usr/share/nginx/html:ro"
- depends_on:
- - web
- - streaming
- restart: always
-
- db:
- image: docker.io/library/postgres:{{ mastodon_database_image_version }}
- mem_limit: 512mb
- memswap_limit: 768mb
- read_only: true
- tmpfs:
- - /run/postgresql:size=512K
- - /tmp:size=128K
- environment:
- - "POSTGRES_HOST_AUTH_METHOD=trust"
- stop_grace_period: 2m
- stop_signal: SIGINT
- networks:
- backend:
- healthcheck:
- test: ['CMD', 'pg_isready', '-U', 'postgres']
- volumes:
- - {{ mastodon_database_location }}:/var/lib/postgresql/data
- restart: always
-
- redis:
- image: docker.io/library/redis:{{ mastodon_redis_image_version }}
- mem_limit: 512mb
- memswap_limit: 768mb
- networks:
- backend:
- healthcheck:
- test: ['CMD', 'redis-cli', 'ping']
- volumes:
- - {{ mastodon_redis_location }}:/data
- restart: always
-
-{% if mastodon_config.enable_elasticsearch is defined and mastodon_config.enable_elasticsearch %}
- es:
- restart: always
- image: docker.elastic.co/elasticsearch/elasticsearch:{{ mastodon_elasticsearch_image_version }}
- mem_limit: 1124mb
- memswap_limit: 1280mb
- environment:
- - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
- - "xpack.license.self_generated.type=basic"
- - "xpack.security.enabled=false"
- - "xpack.watcher.enabled=false"
- - "xpack.graph.enabled=false"
- - "xpack.ml.enabled=false"
- - "bootstrap.memory_lock=true"
- - "cluster.name=es-mastodon"
- - "discovery.type=single-node"
- - "thread_pool.write.queue_size=1000"
- - "ingest.geoip.downloader.enabled=false"
- networks:
- backend:
- healthcheck:
- test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
- ulimits:
- memlock:
- soft: -1
- hard: -1
- nofile:
- soft: 65536
- hard: 65536
- volumes:
- - {{ mastodon_elastic_location }}:/usr/share/elasticsearch/data
-{% endif %}
-
- web:
- image: ghcr.io/mastodon/mastodon:{{ mastodon_image_version }}
- mem_limit: 1024mb
- memswap_limit: 1280mb
- env_file: {{ mastodon_install_location }}/.env.production
- depends_on:
- - db
- - redis
-{% if mastodon_config.enable_elasticsearch is defined and mastodon_config.enable_elasticsearch %}
- - es
-{% endif %}
- volumes:
- - {{ mastodon_public_location }}/system:/mastodon/public/system
- networks:
- frontend:
- backend:
- healthcheck:
- test: ['CMD-SHELL',"curl -s --noproxy localhost localhost:3000/health | grep -q 'OK' || exit 1"]
- restart: always
- command: bundle exec puma -C config/puma.rb
-
- streaming:
- image: ghcr.io/mastodon/mastodon-streaming:{{ mastodon_image_version }}
- mem_limit: 1024mb
- memswap_limit: 1280mb
- env_file: {{ mastodon_install_location }}/.env.production
- networks:
- frontend:
- backend:
- healthcheck:
- test: ['CMD-SHELL', "curl -s --noproxy localhost localhost:4000/api/v1/streaming/health | grep -q 'OK' || exit 1"]
- depends_on:
- - db
- - redis
- restart: always
- command: node ./streaming/index.js
-
- sidekiq:
- image: ghcr.io/mastodon/mastodon:{{ mastodon_image_version }}
- mem_limit: 1024mb
- memswap_limit: 1280mb
- env_file: {{ mastodon_install_location }}/.env.production
- depends_on:
- - db
- - redis
- volumes:
- - {{ mastodon_public_location }}/system:/mastodon/public/system
- networks:
- frontend:
- backend:
- healthcheck:
- test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
- restart: always
- command: bundle exec sidekiq
-
-networks:
- frontend:
- backend:
- internal: true
-{% if proxy_network is defined %}
- {{ proxy_network }}:
- external: true
-{% endif %}
diff --git a/roles/mastodon/templates/mastodon-backend.network.j2 b/roles/mastodon/templates/mastodon-backend.network.j2
new file mode 100644
index 0000000..49ae27d
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-backend.network.j2
@@ -0,0 +1,8 @@
+{{ ansible_managed | comment }}
+
+[Network]
+NetworkName = mastodon_backend
+Driver = bridge
+Internal = true
+# Most containers expect ipv4 here
+IPv6 = false
diff --git a/roles/mastodon/templates/mastodon-elasticsearch.container.j2 b/roles/mastodon/templates/mastodon-elasticsearch.container.j2
new file mode 100644
index 0000000..a154b8b
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-elasticsearch.container.j2
@@ -0,0 +1,43 @@
+{{ ansible_managed | comment }}
+
+[Unit]
+Description = ElasticSearch for Mastodon
+
+[Service]
+Restart = always
+RestartSec = 5s
+
+[Container]
+Image = {{ mastodon_elasticsearch_containerimage }}:{{ mastodon_elasticsearch_image_tag }}
+ContainerName = mastodon-elasticsearch
+
+# To prevent it from using chroot
+User = 1000
+
+HealthCmd = CMD-SHELL curl --silent --fail localhost:9200/_cluster/health || exit 1
+
+# AutoUpdate = registry
+LogDriver = journald
+
+NoNewPrivileges = true
+DropCapability = all
+UserNS = auto:size=65535
+{% if mastodon_elasticsearch_selinux_level != omit %}
+SecurityLabelLevel = {{ mastodon_elasticsearch_selinux_level }}
+{% endif %}
+
+EnvironmentFile = {{ mastodon_install_location }}/mastodon-elasticsearch.env
+
+Network = mastodon-backend.network:alias=backend-mastodon-elasticsearch
+
+Volume = {{ mastodon_elasticsearch_location }}:/usr/share/elasticsearch/data:U
+
+Ulimit = nofile=65536:65536
+Ulimit = memlock=-1:-1
+
+PodmanArgs = --memory={{ mastodon_memory_high }}
+PodmanArgs = --memory-swap={{ mastodon_swap_max }}
+PodmanArgs = --memory-reservation={{ mastodon_memory_low }}
+
+[Install]
+WantedBy = default.target
diff --git a/roles/mastodon/templates/mastodon-elasticsearch.env.j2 b/roles/mastodon/templates/mastodon-elasticsearch.env.j2
new file mode 100644
index 0000000..c4d0fe8
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-elasticsearch.env.j2
@@ -0,0 +1,13 @@
+{{ ansible_managed | comment }}
+
+ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true
+xpack.license.self_generated.type=basic
+xpack.security.enabled=false
+xpack.watcher.enabled=false
+xpack.graph.enabled=false
+xpack.ml.enabled=false
+bootstrap.memory_lock=true
+cluster.name=es-mastodon
+discovery.type=single-node
+thread_pool.write.queue_size=1000
+ingest.geoip.downloader.enabled=false
diff --git a/roles/mastodon/templates/mastodon-frontend.network.j2 b/roles/mastodon/templates/mastodon-frontend.network.j2
new file mode 100644
index 0000000..ab9d6e5
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-frontend.network.j2
@@ -0,0 +1,6 @@
+{{ ansible_managed | comment }}
+
+[Network]
+NetworkName = mastodon_frontend
+Driver = bridge
+IPv6 = true
diff --git a/roles/mastodon/templates/mastodon-nginx.container.j2 b/roles/mastodon/templates/mastodon-nginx.container.j2
new file mode 100644
index 0000000..48c4507
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-nginx.container.j2
@@ -0,0 +1,45 @@
+{{ ansible_managed | comment }}
+
+[Unit]
+Description = Nginx for Mastodon
+Requires = mastodon-web.service mastodon-streaming.service
+After = mastodon-web.service mastodon-streaming.service
+
+[Service]
+Restart = always
+RestartSec = 5s
+
+[Container]
+Image = {{ mastodon_nginx_containerimage }}:{{ mastodon_nginx_image_tag }}
+ContainerName = mastodon-nginx
+
+# AutoUpdate = registry
+LogDriver = journald
+
+ReadOnly = true
+NoNewPrivileges = true
+DropCapability = all
+UserNS = container:mastodon-web
+{% if mastodon_nginx_selinux_level != omit %}
+SecurityLabelLevel = {{ mastodon_nginx_selinux_level }}
+{% endif %}
+
+Network = mastodon-backend.network
+Network = caddy.network:alias=caddy-mastodon-nginx
+ExposeHostPort = 8080
+
+# Equal to mastodon user, so it can stat the public folder
+User = 991
+
+Volume = {{ mastodon_public_location }}:/usr/share/nginx/html/system:ro
+Volume = {{ mastodon_install_location }}/default.conf:/etc/nginx/conf.d/default.conf:U,ro
+
+Tmpfs = /var/cache/nginx:rw,noexec,nosuid,nodev,size=1g
+Tmpfs = /tmp:rw,noexec,nosuid,nodev,size=100m
+
+PodmanArgs = --memory={{ mastodon_nginx_memory_high }}
+PodmanArgs = --memory-swap={{ mastodon_nginx_swap_max }}
+PodmanArgs = --memory-reservation={{ mastodon_nginx_memory_low }}
+
+[Install]
+WantedBy = default.target
diff --git a/roles/mastodon/templates/mastodon-postgres-socket.volume b/roles/mastodon/templates/mastodon-postgres-socket.volume
new file mode 100644
index 0000000..34ba6f7
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-postgres-socket.volume
@@ -0,0 +1,5 @@
+{{ ansible_managed | comment }}
+
+[Volume]
+
+VolumeName = mastodon-postgres-socket
diff --git a/roles/mastodon/templates/mastodon-postgres.container.j2 b/roles/mastodon/templates/mastodon-postgres.container.j2
new file mode 100644
index 0000000..7653c8a
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-postgres.container.j2
@@ -0,0 +1,48 @@
+{{ ansible_managed | comment }}
+
+[Unit]
+Description = Postgres for Mastodon
+
+[Service]
+Restart = always
+RestartSec = 5s
+
+[Container]
+Image = {{ mastodon_postgres_containerimage }}:{{ mastodon_postgres_image_tag }}
+ContainerName = mastodon-postgres
+
+HealthCmd = CMD pg_isready -U postgres
+
+# AutoUpdate = registry
+LogDriver = journald
+
+# User = 70
+# Group = 70
+
+# TODO: Investigate necessary capabilities
+# NoNewPrivileges = true
+ReadOnly = true
+DropCapability = all
+AddCapability = CHOWN DAC_OVERRIDE SETUID SETGID
+UserNS = auto:size=65535
+{% if mastodon_postgres_selinux_level != omit %}
+SecurityLabelLevel = {{ mastodon_postgres_selinux_level }}
+{% endif %}
+
+EnvironmentFile = {{ mastodon_install_location }}/mastodon-postgres.env
+
+Volume = mastodon-postgres-socket:/var/run/postgresql:U,z
+Volume = {{ mastodon_postgres_location }}:/var/lib/postgresql/data:U
+Tmpfs = /run/postgresql:rw,noexec,nosuid,nodev,size=1m
+Tmpfs = /tmp:rw,noexec,nosuid,nodev,size=50m
+
+ExposeHostPort = 5432
+
+PodmanArgs = --memory={{ mastodon_postgres_memory_high }}
+PodmanArgs = --memory-swap={{ mastodon_postgres_swap_max }}
+PodmanArgs = --memory-reservation={{ mastodon_postgres_memory_low }}
+
+PodmanArgs = --stop-signal=SIGINT
+
+[Install]
+WantedBy = default.target
diff --git a/roles/mastodon/templates/mastodon-postgres.env.j2 b/roles/mastodon/templates/mastodon-postgres.env.j2
new file mode 100644
index 0000000..27cd002
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-postgres.env.j2
@@ -0,0 +1,3 @@
+{{ ansible_managed | comment }}
+
+POSTGRES_PASSWORD={{ mastodon_postgres_password }}
diff --git a/roles/mastodon/templates/mastodon-redis-socket.volume b/roles/mastodon/templates/mastodon-redis-socket.volume
new file mode 100644
index 0000000..bbb2ff5
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-redis-socket.volume
@@ -0,0 +1,5 @@
+{{ ansible_managed | comment }}
+
+[Volume]
+
+VolumeName = mastodon-redis-socket
diff --git a/roles/mastodon/templates/mastodon-redis.container.j2 b/roles/mastodon/templates/mastodon-redis.container.j2
new file mode 100644
index 0000000..69a3814
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-redis.container.j2
@@ -0,0 +1,46 @@
+{{ ansible_managed | comment }}
+
+[Unit]
+Description = Redis for Mastodon
+
+[Service]
+Restart = always
+RestartSec = 5s
+
+[Container]
+Image = {{ mastodon_redis_containerimage }}:{{ mastodon_redis_image_tag }}
+ContainerName = mastodon-redis
+
+HealthCmd = CMD redis-cli -s /run/redis/redis.sock ping
+
+Exec = redis-server /usr/lib/redis/redis.conf
+
+# AutoUpdate = registry
+LogDriver = journald
+
+NoNewPrivileges = true
+ReadOnly = true
+DropCapability = all
+AddCapability = CHOWN DAC_OVERRIDE SETUID SETGID
+UserNS = auto:size=65535
+{% if mastodon_redis_selinux_level != omit %}
+SecurityLabelLevel = {{ mastodon_redis_selinux_level }}
+{% endif %}
+
+User = redis
+Group = redis
+
+# Sysctl = vm.overcommit_memory=1
+
+Volume = mastodon-redis-socket:/run/redis:U,z
+Volume = {{ mastodon_install_location }}/redis.conf:/usr/lib/redis/redis.conf:U,ro
+Volume = {{ mastodon_redis_location }}:/data:U
+
+Tmpfs = /run:rw,noexec,nosuid,nodev,size=1m
+
+PodmanArgs = --memory={{ mastodon_redis_memory_high }}
+PodmanArgs = --memory-swap={{ mastodon_redis_swap_max }}
+PodmanArgs = --memory-reservation={{ mastodon_redis_memory_low }}
+
+[Install]
+WantedBy = default.target
diff --git a/roles/mastodon/templates/mastodon-sidekiq.container.j2 b/roles/mastodon/templates/mastodon-sidekiq.container.j2
new file mode 100644
index 0000000..2d438f1
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-sidekiq.container.j2
@@ -0,0 +1,45 @@
+{{ ansible_managed | comment }}
+
+[Unit]
+Description = Mastodon Sidekiq Server
+Requires = mastodon-postgres.service mastodon-redis.service
+After = mastodon-postgres.service mastodon-redis.service
+
+[Service]
+Restart = always
+RestartSec = 5s
+
+[Container]
+Image = {{ mastodon_containerimage }}:{{ mastodon_image_tag }}
+ContainerName = mastodon-sidekiq
+
+HealthCmd = CMD-SHELL ps aux | grep '[s]idekiq\ 6' || false
+
+Exec = bundle exec sidekiq
+
+# AutoUpdate = registry
+LogDriver = journald
+
+NoNewPrivileges = true
+DropCapability = all
+UserNS = container:mastodon-web
+{% if mastodon_selinux_level != omit %}
+SecurityLabelLevel = {{ mastodon_selinux_level }}
+{% endif %}
+
+Network = mastodon-frontend.network
+Network = mastodon-backend.network
+
+EnvironmentFile = {{ mastodon_install_location }}/mastodon.env
+
+Volume = {{ mastodon_public_location }}:/mastodon/public/system
+
+Volume = mastodon-postgres-socket:/var/run/postgresql:z
+Volume = mastodon-redis-socket:/run/redis:z
+
+PodmanArgs = --memory={{ mastodon_memory_high }}
+PodmanArgs = --memory-swap={{ mastodon_swap_max }}
+PodmanArgs = --memory-reservation={{ mastodon_memory_low }}
+
+[Install]
+WantedBy = default.target
diff --git a/roles/mastodon/templates/mastodon-streaming.container.j2 b/roles/mastodon/templates/mastodon-streaming.container.j2
new file mode 100644
index 0000000..dbcc361
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-streaming.container.j2
@@ -0,0 +1,44 @@
+{{ ansible_managed | comment }}
+
+[Unit]
+Description = Mastodon Streaming Server
+Requires = mastodon-postgres.service mastodon-redis.service
+After = mastodon-postgres.service mastodon-redis.service
+
+[Service]
+Restart = always
+RestartSec = 5s
+
+[Container]
+Image = {{ mastodon_streaming_containerimage }}:{{ mastodon_image_tag }}
+ContainerName = mastodon-streaming
+
+HealthCmd = CMD-SHELL curl -s --noproxy localhost localhost:4000/api/v1/streaming/health | grep -q 'OK' || exit 1
+
+Exec = node ./streaming/index.js
+
+AutoUpdate = registry
+LogDriver = journald
+
+NoNewPrivileges = true
+DropCapability = all
+UserNS = auto:size=65535
+{% if mastodon_selinux_level != omit %}
+SecurityLabelLevel = {{ mastodon_selinux_level }}
+{% endif %}
+
+Network = mastodon-backend.network:alias=backend-mastodon-streaming
+
+ExposeHostPort = 4000
+
+EnvironmentFile = {{ mastodon_install_location }}/mastodon.env
+
+Volume = mastodon-postgres-socket:/var/run/postgresql:z
+Volume = mastodon-redis-socket:/run/redis:z
+
+PodmanArgs = --memory={{ mastodon_memory_high }}
+PodmanArgs = --memory-swap={{ mastodon_swap_max }}
+PodmanArgs = --memory-reservation={{ mastodon_memory_low }}
+
+[Install]
+WantedBy = default.target
diff --git a/roles/mastodon/templates/mastodon-web.container.j2 b/roles/mastodon/templates/mastodon-web.container.j2
new file mode 100644
index 0000000..655270a
--- /dev/null
+++ b/roles/mastodon/templates/mastodon-web.container.j2
@@ -0,0 +1,45 @@
+{{ ansible_managed | comment }}
+
+[Unit]
+Description = Mastodon Web Server
+Requires = mastodon-postgres.service mastodon-redis.service mastodon-elasticsearch.service
+After = mastodon-postgres.service mastodon-redis.service mastodon-elasticsearch.service
+
+[Service]
+Restart = always
+RestartSec = 5s
+
+[Container]
+Image = {{ mastodon_containerimage }}:{{ mastodon_image_tag }}
+ContainerName = mastodon-web
+
+HealthCmd = CMD-SHELL curl -s --noproxy localhost localhost:3000/health | grep -q 'OK' || exit 1
+
+Exec = bundle exec puma -C config/puma.rb
+
+# AutoUpdate = registry
+LogDriver = journald
+
+NoNewPrivileges = true
+DropCapability = all
+UserNS = auto:size=65535
+{% if mastodon_selinux_level != omit %}
+SecurityLabelLevel = {{ mastodon_selinux_level }}
+{% endif %}
+
+Network = mastodon-frontend.network
+Network = mastodon-backend.network:alias=backend-mastodon-web
+
+EnvironmentFile = {{ mastodon_install_location }}/mastodon.env
+
+Volume = {{ mastodon_public_location }}:/mastodon/public/system:U
+
+Volume = mastodon-postgres-socket:/var/run/postgresql:z
+Volume = mastodon-redis-socket:/run/redis:z
+
+PodmanArgs = --memory={{ mastodon_memory_high }}
+PodmanArgs = --memory-swap={{ mastodon_swap_max }}
+PodmanArgs = --memory-reservation={{ mastodon_memory_low }}
+
+[Install]
+WantedBy = default.target
diff --git a/roles/mastodon/templates/mastodon.caddy.j2 b/roles/mastodon/templates/mastodon.caddy.j2
new file mode 100644
index 0000000..70a1420
--- /dev/null
+++ b/roles/mastodon/templates/mastodon.caddy.j2
@@ -0,0 +1,31 @@
+{{ ansible_managed | comment }}
+
+{{ mastodon_domain }} {
+ encode gzip
+
+ header {
+ # enable HSTS
+ Strict-Transport-Security "max-age=31536000; preload;"
+
+ # disable clients from sniffing the media type
+ X-Content-Type-Options nosniff
+
+ # clickjacking protection
+ X-Frame-Options DENY
+
+ # keep referrer data off of HTTP connections
+ Referrer-Policy no-referrer-when-downgrade
+
+ # Server name removing
+ -Server
+ }
+
+ # TODO: Move to own caddy file
+ route /.well-known/matrix* {
+ reverse_proxy https://matrix.flausch.social {
+ header_up -Host
+ }
+ }
+
+ reverse_proxy caddy-mastodon-nginx:8080
+}
diff --git a/roles/mastodon/templates/mastodon.env.j2 b/roles/mastodon/templates/mastodon.env.j2
new file mode 100644
index 0000000..86fc561
--- /dev/null
+++ b/roles/mastodon/templates/mastodon.env.j2
@@ -0,0 +1,113 @@
+{{ ansible_managed | comment }}
+
+# Redis
+# -----
+REDIS_URL=unix:///run/redis/redis.sock
+
+# PostgreSQL
+# ----------
+DB_HOST=/var/run/postgresql
+DB_USER=postgres
+DB_NAME=postgres
+DB_PASS={{ mastodon_postgres_password }}
+DB_PORT=5432
+
+# Elasticsearch
+# ------------------------
+ES_ENABLED=true
+ES_HOST=backend-mastodon-elasticsearch
+ES_PORT=9200
+
+# Federation
+# ----------
+# This identifies your server and cannot be changed safely later
+# ----------
+LOCAL_DOMAIN={{ mastodon_config.local_domain }}
+
+{% if mastodon_config.web_domain is defined %}
+# 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_config.web_domain }}
+
+{% endif %}
+# 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
+
+# Secrets
+# -------
+# Make sure to use `bundle exec rails secret` to generate secrets
+# -------
+SECRET_KEY_BASE={{ mastodon_config.secret_key_base }}
+OTP_SECRET={{ mastodon_config.otp_secret }}
+
+# Encryption secrets
+# ------------------
+# Must be available (and set to same values) for all server processes
+# These are private/secret values, do not share outside hosting environment
+# Use `bin/rails db:encryption:init` to generate fresh secrets
+# Do not change these secrets once in use, as this would cause data loss and other issues
+# ------------------
+ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY={{ mastodon_config.ar_enc_deterministic_key }}
+ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT={{ mastodon_config.ar_enc_derivation_salt }}
+ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY={{ mastodon_config.ar_enc_primary_key }}
+
+# Web Push
+# --------
+# Generate with `bundle exec rails mastodon:webpush:generate_vapid_key`
+# --------
+VAPID_PRIVATE_KEY={{ mastodon_config.vapid_private_key }}
+VAPID_PUBLIC_KEY={{ mastodon_config.vapid_public_key }}
+
+
+# Registrations
+# ------------
+{% if mastodon_config.single_user_mode is defined %}
+# Single user mode will disable registrations and redirect frontpage to the first profile
+SINGLE_USER_MODE={{ mastodon_config.single_user_mode | bool | lower }}
+{% endif %}
+# 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
+
+
+# Sending mail
+# ------------
+SMTP_SERVER={{ mastodon_config.smtp_server }}
+SMTP_PORT={{ mastodon_config.smtp_port | default(587) }}
+SMTP_LOGIN={{ mastodon_config.smtp_login }}
+SMTP_PASSWORD={{ mastodon_config.smtp_password }}
+SMTP_FROM_ADDRESS={{ mastodon_config.smtp_from_address }}
+#SMTP_REPLY_TO=
+#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
+#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
+#SMTP_AUTH_METHOD=plain
+#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
+#SMTP_OPENSSL_VERIFY_MODE=peer
+#SMTP_ENABLE_STARTTLS_AUTO=true
+#SMTP_TLS=true
+
+
+# File storage (optional)
+# -----------------------
+# S3 (Minio Config (optional) Please check Minio instance for details)
+# 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:
+# -----------------------
+# 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=
+
diff --git a/roles/mastodon/templates/redis.conf.j2 b/roles/mastodon/templates/redis.conf.j2
new file mode 100644
index 0000000..9264036
--- /dev/null
+++ b/roles/mastodon/templates/redis.conf.j2
@@ -0,0 +1,513 @@
+{{ ansible_managed | comment }}
+
+# Only listen on localhost
+bind 127.0.0.1 -::1
+
+# When protected mode is on and the default user has no password, the server
+# only accepts local connections from the IPv4 address (127.0.0.1), IPv6 address
+# (::1) or Unix domain sockets.
+protected-mode yes
+
+# Redis uses default hardened security configuration directives to reduce the
+# attack surface on innocent users. Therefore, several sensitive configuration
+# directives are immutable, and some potentially-dangerous commands are blocked.
+#
+# Configuration directives that control files that Redis writes to (e.g., 'dir'
+# and 'dbfilename') and that aren't usually modified during runtime
+# are protected by making them immutable.
+#
+# Commands that can increase the attack surface of Redis and that aren't usually
+# called by users are blocked by default.
+#
+# These can be exposed to either all connections or just local ones by setting
+# each of the configs listed below to either of these values:
+#
+# no - Block for any connection (remain immutable)
+# yes - Allow for any connection (no protection)
+# local - Allow only for local connections. Ones originating from the
+# IPv4 address (127.0.0.1), IPv6 address (::1) or Unix domain sockets.
+enable-protected-configs no
+enable-debug-command no
+enable-module-command no
+
+# Accept connections on the specified port, default is 6379 (IANA #815344).
+# If port 0 is specified Redis will not listen on a TCP socket.
+port 0
+
+# Unix socket.
+unixsocket /run/redis/redis.sock
+unixsocketperm 777
+
+# Close the connection after a client is idle for N seconds (0 to disable)
+timeout 0
+
+# Apply OS-specific mechanism to mark the listening socket with the specified
+# ID, to support advanced routing and filtering capabilities.
+#
+# On Linux, the ID represents a connection mark.
+# On FreeBSD, the ID represents a socket cookie ID.
+# On OpenBSD, the ID represents a route table ID.
+#
+# The default value is 0, which implies no marking is required.
+# socket-mark-id 0
+
+################################# GENERAL #####################################
+
+# By default Redis does not run as a daemon. Use 'yes' if you need it.
+# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
+# When Redis is supervised by upstart or systemd, this parameter has no impact.
+daemonize no
+
+# If you run Redis from upstart or systemd, Redis can interact with your
+# supervision tree. Options:
+# supervised no - no supervision interaction
+# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
+# requires "expect stop" in your upstart job config
+# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
+# on startup, and updating Redis status on a regular
+# basis.
+# supervised auto - detect upstart or systemd method based on
+# UPSTART_JOB or NOTIFY_SOCKET environment variables
+# Note: these supervision methods only signal "process is ready."
+# They do not enable continuous pings back to your supervisor.
+#
+# The default is "no". To run under upstart/systemd, you can simply uncomment
+# the line below:
+supervised auto
+
+pidfile /run/redis_6379.pid
+
+# Specify the server verbosity level.
+# This can be one of:
+# debug (a lot of information, useful for development/testing)
+# verbose (many rarely useful info, but not a mess like the debug level)
+# notice (moderately verbose, what you want in production probably)
+# warning (only very important / critical messages are logged)
+# nothing (nothing is logged)
+loglevel notice
+
+# Specify the log file name. Also the empty string can be used to force
+# Redis to log on the standard output. Note that if you use standard
+# output for logging but daemonize, logs will be sent to /dev/null
+logfile ""
+
+# Set the number of databases. The default database is DB 0, you can select
+# a different one on a per-connection basis using SELECT where
+# dbid is a number between 0 and 'databases'-1
+databases 16
+
+# Set the local environment which is used for string comparison operations, and
+# also affect the performance of Lua scripts. Empty String indicates the locale
+# is derived from the environment variables.
+locale-collate ""
+
+################################ SNAPSHOTTING ################################
+
+# Save the DB to disk.
+#
+# save [ ...]
+#
+# Redis will save the DB if the given number of seconds elapsed and it
+# surpassed the given number of write operations against the DB.
+#
+# Snapshotting can be completely disabled with a single empty string argument
+# as in following example:
+#
+# save ""
+#
+# Unless specified otherwise, by default Redis will save the DB:
+# * After 3600 seconds (an hour) if at least 1 change was performed
+# * After 300 seconds (5 minutes) if at least 100 changes were performed
+# * After 60 seconds if at least 10000 changes were performed
+#
+# You can set these explicitly by uncommenting the following line.
+#
+save 3600 1 300 100 60 10000
+
+# By default Redis will stop accepting writes if RDB snapshots are enabled
+# (at least one save point) and the latest background save failed.
+# This will make the user aware (in a hard way) that data is not persisting
+# on disk properly, otherwise chances are that no one will notice and some
+# disaster will happen.
+#
+# If the background saving process will start working again Redis will
+# automatically allow writes again.
+#
+# However if you have setup your proper monitoring of the Redis server
+# and persistence, you may want to disable this feature so that Redis will
+# continue to work as usual even if there are problems with disk,
+# permissions, and so forth.
+stop-writes-on-bgsave-error yes
+
+# Compress string objects using LZF when dump .rdb databases?
+# By default compression is enabled as it's almost always a win.
+# If you want to save some CPU in the saving child set it to 'no' but
+# the dataset will likely be bigger if you have compressible values or keys.
+rdbcompression yes
+
+# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
+# This makes the format more resistant to corruption but there is a performance
+# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
+# for maximum performances.
+#
+# RDB files created with checksum disabled have a checksum of zero that will
+# tell the loading code to skip the check.
+rdbchecksum yes
+
+# Enables or disables full sanitization checks for ziplist and listpack etc when
+# loading an RDB or RESTORE payload. This reduces the chances of a assertion or
+# crash later on while processing commands.
+# Options:
+# no - Never perform full sanitization
+# yes - Always perform full sanitization
+# clients - Perform full sanitization only for user connections.
+# Excludes: RDB files, RESTORE commands received from the master
+# connection, and client connections which have the
+# skip-sanitize-payload ACL flag.
+# The default should be 'clients' but since it currently affects cluster
+# resharding via MIGRATE, it is temporarily set to 'no' by default.
+#
+sanitize-dump-payload clients
+
+# The filename where to dump the DB
+dbfilename dump.rdb
+
+# The working directory.
+#
+# The DB will be written inside this directory, with the filename specified
+# above using the 'dbfilename' configuration directive.
+#
+# The Append Only File will also be created inside this directory.
+dir /data
+
+################################## SECURITY ###################################
+
+# Warning: since Redis is pretty fast, an outside user can try up to
+# 1 million passwords per second against a modern box. This means that you
+# should use very strong passwords, otherwise they will be very easy to break.
+# Note that because the password is really a shared secret between the client
+# and the server, and should not be memorized by any human, the password
+# can be easily a long string from /dev/urandom or whatever, so by using a
+# long and unguessable password no brute force attack will be possible.
+
+# Redis ACL users are defined in the following format:
+#
+# user ... acl rules ...
+#
+# For example:
+#
+# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
+#
+# The special username "default" is used for new connections. If this user
+# has the "nopass" rule, then new connections will be immediately authenticated
+# as the "default" user without the need of any password provided via the
+# AUTH command. Otherwise if the "default" user is not flagged with "nopass"
+# the connections will start in not authenticated state, and will require
+# AUTH (or the HELLO command AUTH option) in order to be authenticated and
+# start to work.
+#
+# The ACL rules that describe what a user can do are the following:
+#
+# on Enable the user: it is possible to authenticate as this user.
+# off Disable the user: it's no longer possible to authenticate
+# with this user, however the already authenticated connections
+# will still work.
+# skip-sanitize-payload RESTORE dump-payload sanitization is skipped.
+# sanitize-payload RESTORE dump-payload is sanitized (default).
+# + Allow the execution of that command.
+# May be used with `|` for allowing subcommands (e.g "+config|get")
+# - Disallow the execution of that command.
+# May be used with `|` for blocking subcommands (e.g "-config|set")
+# +@ Allow the execution of all the commands in such category
+# with valid categories are like @admin, @set, @sortedset, ...
+# and so forth, see the full list in the server.c file where
+# the Redis command table is described and defined.
+# The special category @all means all the commands, but currently
+# present in the server, and that will be loaded in the future
+# via modules.
+# +|first-arg Allow a specific first argument of an otherwise
+# disabled command. It is only supported on commands with
+# no sub-commands, and is not allowed as negative form
+# like -SELECT|1, only additive starting with "+". This
+# feature is deprecated and may be removed in the future.
+# allcommands Alias for +@all. Note that it implies the ability to execute
+# all the future commands loaded via the modules system.
+# nocommands Alias for -@all.
+# ~ Add a pattern of keys that can be mentioned as part of
+# commands. For instance ~* allows all the keys. The pattern
+# is a glob-style pattern like the one of KEYS.
+# It is possible to specify multiple patterns.
+# %R~ Add key read pattern that specifies which keys can be read
+# from.
+# %W~ Add key write pattern that specifies which keys can be
+# written to.
+# allkeys Alias for ~*
+# resetkeys Flush the list of allowed keys patterns.
+# & Add a glob-style pattern of Pub/Sub channels that can be
+# accessed by the user. It is possible to specify multiple channel
+# patterns.
+# allchannels Alias for &*
+# resetchannels Flush the list of allowed channel patterns.
+# > Add this password to the list of valid password for the user.
+# For example >mypass will add "mypass" to the list.
+# This directive clears the "nopass" flag (see later).
+# < Remove this password from the list of valid passwords.
+# nopass All the set passwords of the user are removed, and the user
+# is flagged as requiring no password: it means that every
+# password will work against this user. If this directive is
+# used for the default user, every new connection will be
+# immediately authenticated with the default user without
+# any explicit AUTH command required. Note that the "resetpass"
+# directive will clear this condition.
+# resetpass Flush the list of allowed passwords. Moreover removes the
+# "nopass" status. After "resetpass" the user has no associated
+# passwords and there is no way to authenticate without adding
+# some password (or setting it as "nopass" later).
+# reset Performs the following actions: resetpass, resetkeys, resetchannels,
+# allchannels (if acl-pubsub-default is set), off, clearselectors, -@all.
+# The user returns to the same state it has immediately after its creation.
+# () Create a new selector with the options specified within the
+# parentheses and attach it to the user. Each option should be
+# space separated. The first character must be ( and the last
+# character must be ).
+# clearselectors Remove all of the currently attached selectors.
+# Note this does not change the "root" user permissions,
+# which are the permissions directly applied onto the
+# user (outside the parentheses).
+#
+# ACL rules can be specified in any order: for instance you can start with
+# passwords, then flags, or key patterns. However note that the additive
+# and subtractive rules will CHANGE MEANING depending on the ordering.
+# For instance see the following example:
+#
+# user alice on +@all -DEBUG ~* >somepassword
+#
+# This will allow "alice" to use all the commands with the exception of the
+# DEBUG command, since +@all added all the commands to the set of the commands
+# alice can use, and later DEBUG was removed. However if we invert the order
+# of two ACL rules the result will be different:
+#
+# user alice on -DEBUG +@all ~* >somepassword
+#
+# Now DEBUG was removed when alice had yet no commands in the set of allowed
+# commands, later all the commands are added, so the user will be able to
+# execute everything.
+#
+# Basically ACL rules are processed left-to-right.
+#
+# The following is a list of command categories and their meanings:
+# * keyspace - Writing or reading from keys, databases, or their metadata
+# in a type agnostic way. Includes DEL, RESTORE, DUMP, RENAME, EXISTS, DBSIZE,
+# KEYS, EXPIRE, TTL, FLUSHALL, etc. Commands that may modify the keyspace,
+# key or metadata will also have `write` category. Commands that only read
+# the keyspace, key or metadata will have the `read` category.
+# * read - Reading from keys (values or metadata). Note that commands that don't
+# interact with keys, will not have either `read` or `write`.
+# * write - Writing to keys (values or metadata)
+# * admin - Administrative commands. Normal applications will never need to use
+# these. Includes REPLICAOF, CONFIG, DEBUG, SAVE, MONITOR, ACL, SHUTDOWN, etc.
+# * dangerous - Potentially dangerous (each should be considered with care for
+# various reasons). This includes FLUSHALL, MIGRATE, RESTORE, SORT, KEYS,
+# CLIENT, DEBUG, INFO, CONFIG, SAVE, REPLICAOF, etc.
+# * connection - Commands affecting the connection or other connections.
+# This includes AUTH, SELECT, COMMAND, CLIENT, ECHO, PING, etc.
+# * blocking - Potentially blocking the connection until released by another
+# command.
+# * fast - Fast O(1) commands. May loop on the number of arguments, but not the
+# number of elements in the key.
+# * slow - All commands that are not Fast.
+# * pubsub - PUBLISH / SUBSCRIBE related
+# * transaction - WATCH / MULTI / EXEC related commands.
+# * scripting - Scripting related.
+# * set - Data type: sets related.
+# * sortedset - Data type: zsets related.
+# * list - Data type: lists related.
+# * hash - Data type: hashes related.
+# * string - Data type: strings related.
+# * bitmap - Data type: bitmaps related.
+# * hyperloglog - Data type: hyperloglog related.
+# * geo - Data type: geo related.
+# * stream - Data type: streams related.
+#
+# For more information about ACL configuration please refer to
+# the Redis web site at https://redis.io/topics/acl
+
+# ACL LOG
+#
+# The ACL Log tracks failed commands and authentication events associated
+# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
+# by ACLs. The ACL Log is stored in memory. You can reclaim memory with
+# ACL LOG RESET. Define the maximum entry length of the ACL Log below.
+acllog-max-len 128
+
+#################### KERNEL transparent hugepage CONTROL ######################
+
+# Usually the kernel Transparent Huge Pages control is set to "madvise" or
+# or "never" by default (/sys/kernel/mm/transparent_hugepage/enabled), in which
+# case this config has no effect. On systems in which it is set to "always",
+# redis will attempt to disable it specifically for the redis process in order
+# to avoid latency problems specifically with fork(2) and CoW.
+# If for some reason you prefer to keep it enabled, you can set this config to
+# "no" and the kernel global to "always".
+disable-thp yes
+
+############################## APPEND ONLY MODE ###############################
+
+# By default Redis asynchronously dumps the dataset on disk. This mode is
+# good enough in many applications, but an issue with the Redis process or
+# a power outage may result into a few minutes of writes lost (depending on
+# the configured save points).
+#
+# The Append Only File is an alternative persistence mode that provides
+# much better durability. For instance using the default data fsync policy
+# (see later in the config file) Redis can lose just one second of writes in a
+# dramatic event like a server power outage, or a single write if something
+# wrong with the Redis process itself happens, but the operating system is
+# still running correctly.
+#
+# AOF and RDB persistence can be enabled at the same time without problems.
+# If the AOF is enabled on startup Redis will load the AOF, that is the file
+# with the better durability guarantees.
+#
+# Note that changing this value in a config file of an existing database and
+# restarting the server can lead to data loss. A conversion needs to be done
+# by setting it via CONFIG command on a live server first.
+#
+# Please check https://redis.io/topics/persistence for more information.
+
+appendonly no
+
+# The base name of the append only file.
+#
+# Redis 7 and newer use a set of append-only files to persist the dataset
+# and changes applied to it. There are two basic types of files in use:
+#
+# - Base files, which are a snapshot representing the complete state of the
+# dataset at the time the file was created. Base files can be either in
+# the form of RDB (binary serialized) or AOF (textual commands).
+# - Incremental files, which contain additional commands that were applied
+# to the dataset following the previous file.
+#
+# In addition, manifest files are used to track the files and the order in
+# which they were created and should be applied.
+#
+# Append-only file names are created by Redis following a specific pattern.
+# The file name's prefix is based on the 'appendfilename' configuration
+# parameter, followed by additional information about the sequence and type.
+#
+# For example, if appendfilename is set to appendonly.aof, the following file
+# names could be derived:
+#
+# - appendonly.aof.1.base.rdb as a base file.
+# - appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof as incremental files.
+# - appendonly.aof.manifest as a manifest file.
+
+appendfilename "appendonly.aof"
+
+# For convenience, Redis stores all persistent append-only files in a dedicated
+# directory. The name of the directory is determined by the appenddirname
+# configuration parameter.
+
+appenddirname "appendonlydir"
+
+# The fsync() call tells the Operating System to actually write data on disk
+# instead of waiting for more data in the output buffer. Some OS will really flush
+# data on disk, some other OS will just try to do it ASAP.
+#
+# Redis supports three different modes:
+#
+# no: don't fsync, just let the OS flush the data when it wants. Faster.
+# always: fsync after every write to the append only log. Slow, Safest.
+# everysec: fsync only one time every second. Compromise.
+#
+# The default is "everysec", as that's usually the right compromise between
+# speed and data safety. It's up to you to understand if you can relax this to
+# "no" that will let the operating system flush the output buffer when
+# it wants, for better performances (but if you can live with the idea of
+# some data loss consider the default persistence mode that's snapshotting),
+# or on the contrary, use "always" that's very slow but a bit safer than
+# everysec.
+#
+# More details please check the following article:
+# http://antirez.com/post/redis-persistence-demystified.html
+#
+# If unsure, use "everysec".
+
+# appendfsync always
+appendfsync everysec
+# appendfsync no
+
+# When the AOF fsync policy is set to always or everysec, and a background
+# saving process (a background save or AOF log background rewriting) is
+# performing a lot of I/O against the disk, in some Linux configurations
+# Redis may block too long on the fsync() call. Note that there is no fix for
+# this currently, as even performing fsync in a different thread will block
+# our synchronous write(2) call.
+#
+# In order to mitigate this problem it's possible to use the following option
+# that will prevent fsync() from being called in the main process while a
+# BGSAVE or BGREWRITEAOF is in progress.
+#
+# This means that while another child is saving, the durability of Redis is
+# the same as "appendfsync no". In practical terms, this means that it is
+# possible to lose up to 30 seconds of log in the worst scenario (with the
+# default Linux settings).
+#
+# If you have latency problems turn this to "yes". Otherwise leave it as
+# "no" that is the safest pick from the point of view of durability.
+
+no-appendfsync-on-rewrite no
+
+# Automatic rewrite of the append only file.
+# Redis is able to automatically rewrite the log file implicitly calling
+# BGREWRITEAOF when the AOF log size grows by the specified percentage.
+#
+# This is how it works: Redis remembers the size of the AOF file after the
+# latest rewrite (if no rewrite has happened since the restart, the size of
+# the AOF at startup is used).
+#
+# This base size is compared to the current size. If the current size is
+# bigger than the specified percentage, the rewrite is triggered. Also
+# you need to specify a minimal size for the AOF file to be rewritten, this
+# is useful to avoid rewriting the AOF file even if the percentage increase
+# is reached but it is still pretty small.
+#
+# Specify a percentage of zero in order to disable the automatic AOF
+# rewrite feature.
+
+auto-aof-rewrite-percentage 100
+auto-aof-rewrite-min-size 64mb
+
+# An AOF file may be found to be truncated at the end during the Redis
+# startup process, when the AOF data gets loaded back into memory.
+# This may happen when the system where Redis is running
+# crashes, especially when an ext4 filesystem is mounted without the
+# data=ordered option (however this can't happen when Redis itself
+# crashes or aborts but the operating system still works correctly).
+#
+# Redis can either exit with an error when this happens, or load as much
+# data as possible (the default now) and start if the AOF file is found
+# to be truncated at the end. The following option controls this behavior.
+#
+# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
+# the Redis server starts emitting a log to inform the user of the event.
+# Otherwise if the option is set to no, the server aborts with an error
+# and refuses to start. When the option is set to no, the user requires
+# to fix the AOF file using the "redis-check-aof" utility before to restart
+# the server.
+#
+# Note that if the AOF file will be found to be corrupted in the middle
+# the server will still exit with an error. This option only applies when
+# Redis will try to read more data from the AOF file but not enough bytes
+# will be found.
+aof-load-truncated yes
+
+# Redis can create append-only base files in either RDB or AOF formats. Using
+# the RDB format is always faster and more efficient, and disabling it is only
+# supported for backward compatibility purposes.
+aof-use-rdb-preamble yes
+
+# Redis supports recording timestamp annotations in the AOF to support restoring
+# the data from a specific point-in-time. However, using this capability changes
+# the AOF format in a way that may not be compatible with existing AOF parsers.
+aof-timestamp-enabled no
diff --git a/roles/mastodon/vars/main.yml b/roles/mastodon/vars/main.yml
new file mode 100644
index 0000000..b47af67
--- /dev/null
+++ b/roles/mastodon/vars/main.yml
@@ -0,0 +1,5 @@
+# The container data volume mount locations
+mastodon_postgres_location: "{{ mastodon_install_location }}/postgres"
+mastodon_public_location: "{{ mastodon_install_location }}/public"
+mastodon_redis_location: "{{ mastodon_install_location }}/redis"
+mastodon_elasticsearch_location: "{{ mastodon_install_location }}/elasticsearch"