aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStonewall Jackson <stonewall@sacredheartsc.com>2023-06-12 21:02:22 -0400
committerStonewall Jackson <stonewall@sacredheartsc.com>2023-06-12 21:02:22 -0400
commit865e2f05621fc10f3d332d3840707997c0b94abf (patch)
treeb5f0c85951175b813996991298501c6afb012824
parent78fd379d33bd6853123c02a76c97ca382aa24be9 (diff)
downloadselfhosted-865e2f05621fc10f3d332d3840707997c0b94abf.tar.gz
selfhosted-865e2f05621fc10f3d332d3840707997c0b94abf.zip
add mastodon role
-rw-r--r--inventory-example/10-hosts1
-rw-r--r--inventory-example/20-by-hostname.yml1
-rw-r--r--inventory-example/group_vars/all/firefox.yml2
-rw-r--r--inventory-example/group_vars/all/freeipa.yml3
-rw-r--r--inventory-example/group_vars/all/mastodon.yml13
-rw-r--r--inventory-example/group_vars/all/nsd.yml1
-rw-r--r--inventory-example/group_vars/all/postgres.yml1
-rw-r--r--inventory-example/group_vars/all/vault.yml7
-rw-r--r--playbooks/mastodon.yml17
-rw-r--r--playbooks/populate_domain.yml2
-rw-r--r--playbooks/site.yml1
-rw-r--r--playbooks/webserver_public_example.yml1
-rw-r--r--roles/freeipa_server/files/usr/local/share/dirsrv/schema/mastodon.ldif3
-rw-r--r--roles/freeipa_server/tasks/custom_schema.yml51
-rw-r--r--roles/mastodon/defaults/main.yml23
-rw-r--r--roles/mastodon/handlers/main.yml8
-rw-r--r--roles/mastodon/meta/main.yml14
-rw-r--r--roles/mastodon/tasks/database.yml23
-rw-r--r--roles/mastodon/tasks/freeipa.yml8
-rw-r--r--roles/mastodon/tasks/main.yml121
-rw-r--r--roles/mastodon/templates/etc/systemd/system/mastodon-cleanup.service.j249
-rw-r--r--roles/mastodon/templates/etc/systemd/system/mastodon-cleanup.timer.j210
-rw-r--r--roles/mastodon/templates/etc/systemd/system/mastodon-sidekiq.service.j252
-rw-r--r--roles/mastodon/templates/etc/systemd/system/mastodon-streaming.service.j251
-rw-r--r--roles/mastodon/templates/etc/systemd/system/mastodon-web.service.j252
-rw-r--r--roles/mastodon/templates/opt/mastodon/mastodon/.env.production.j259
-rw-r--r--roles/mastodon/vars/main.yml58
27 files changed, 632 insertions, 0 deletions
diff --git a/inventory-example/10-hosts b/inventory-example/10-hosts
index 90e1acf..43306d6 100644
--- a/inventory-example/10-hosts
+++ b/inventory-example/10-hosts
@@ -36,6 +36,7 @@ dmz-xmpp1 ip=10.10.19.5 cname=xmpp
dmz-turn1 ip=10.10.19.6 cname=turn
dmz-git1 ip=10.10.19.13
dmz-matrix1 ip=10.10.19.14 cores=4 ram=8g disk=256g
+dmz-mastodon1 ip=10.10.19.15 cores=4 ram=4g disk=256g
dmz-asterisk1 ip=10.10.14.10 cname=asterisk cores=4
[unmanaged]
diff --git a/inventory-example/20-by-hostname.yml b/inventory-example/20-by-hostname.yml
index db1ba15..de5f253 100644
--- a/inventory-example/20-by-hostname.yml
+++ b/inventory-example/20-by-hostname.yml
@@ -42,3 +42,4 @@ groups:
turn_servers: inventory_hostname is match('(dmz-)?turn[0-9]')
asterisk_servers: inventory_hostname is match('(dmz-)?asterisk[0-9]')
matrix_servers: inventory_hostname is match('(dmz-)?matrix[0-9]')
+ mastodon_servers: inventory_hostname is match('(dmz-)?mastodon[0-9]')
diff --git a/inventory-example/group_vars/all/firefox.yml b/inventory-example/group_vars/all/firefox.yml
index 07d227b..36d7b1f 100644
--- a/inventory-example/group_vars/all/firefox.yml
+++ b/inventory-example/group_vars/all/firefox.yml
@@ -49,6 +49,8 @@ firefox_managed_bookmarks:
url: 'https://invidious.{{ domain }}'
- name: Jellyfin
url: 'https://jellyfin.{{ domain }}'
+ - name: Mastodon
+ url: 'https://mastodon.{{ domain }}'
- name: Matrix
url: 'https://matrix.{{ domain }}'
- name: Nagios
diff --git a/inventory-example/group_vars/all/freeipa.yml b/inventory-example/group_vars/all/freeipa.yml
index 15b7259..5026212 100644
--- a/inventory-example/group_vars/all/freeipa.yml
+++ b/inventory-example/group_vars/all/freeipa.yml
@@ -131,6 +131,9 @@ freeipa_groups:
- name: role-matrix-access
group: doefamily
+ - name: role-mastodon-access
+ group: doefamily
+
freeipa_hbac_rules:
- name: sysadmins_ssh_and_console_to_all
description: allow sysadmins to ssh to all hosts
diff --git a/inventory-example/group_vars/all/mastodon.yml b/inventory-example/group_vars/all/mastodon.yml
new file mode 100644
index 0000000..932d51d
--- /dev/null
+++ b/inventory-example/group_vars/all/mastodon.yml
@@ -0,0 +1,13 @@
+mastodon_domain: example.com
+mastodon_web_domain: mastodon.example.com
+mastodon_db_name: mastodon
+mastodon_db_user: s-mastodon
+mastodon_db_password: '{{ vault_mastodon_db_password }}'
+mastodon_sysaccount_password: '{{ vault_mastodon_sysaccount_password }}'
+mastodon_login_cidrs:
+ - '{{ vlans.trusted.cidr }}'
+
+mastodon_secret_key_base: '{{ vault_mastodon_secret_key_base }}'
+mastodon_otp_secret: '{{ vault_mastodon_otp_secret }}'
+mastodon_vapid_private_key: '{{ vault_mastodon_vapid_private_key }}'
+mastodon_vapid_public_key: changeme
diff --git a/inventory-example/group_vars/all/nsd.yml b/inventory-example/group_vars/all/nsd.yml
index d40351b..2c21a70 100644
--- a/inventory-example/group_vars/all/nsd.yml
+++ b/inventory-example/group_vars/all/nsd.yml
@@ -35,6 +35,7 @@ nsd_zones:
turn1 IN A 203.0.113.58
pbx1 IN A 203.0.113.59
matrix IN A 203.0.113.60
+ mastodon IN A 203.0.113.61
www IN CNAME www1
xmpp IN CNAME xmpp1
conference IN CNAME xmpp1
diff --git a/inventory-example/group_vars/all/postgres.yml b/inventory-example/group_vars/all/postgres.yml
index be90568..b38f4c0 100644
--- a/inventory-example/group_vars/all/postgres.yml
+++ b/inventory-example/group_vars/all/postgres.yml
@@ -2,3 +2,4 @@ postgresql_host: postgres.{{ domain }}
postgresql_inventory_host: "{{ postgresql_host.split('.')[0] }}"
postgresql_password_users:
- '{{ invidious_db_user }}'
+ - '{{ mastodon_db_user }}'
diff --git a/inventory-example/group_vars/all/vault.yml b/inventory-example/group_vars/all/vault.yml
index 58b597a..18b57bb 100644
--- a/inventory-example/group_vars/all/vault.yml
+++ b/inventory-example/group_vars/all/vault.yml
@@ -65,6 +65,13 @@ vault_invidious_hmac_key: changeme
vault_jellyfin_sysaccount_password: changeme
+# mastodon
+vault_mastodon_sysaccount_password: changeme
+vault_mastodon_secret_key_base: changeme
+vault_mastodon_otp_secret: changeme
+vault_mastodon_vapid_private_key: changeme
+
+
# mediawiki
vault_mediawiki_admin_password: changeme
vault_mediawiki_upgrade_key: changeme
diff --git a/playbooks/mastodon.yml b/playbooks/mastodon.yml
new file mode 100644
index 0000000..8c7b381
--- /dev/null
+++ b/playbooks/mastodon.yml
@@ -0,0 +1,17 @@
+- name: configure mastodon servers
+ hosts: mastodon_servers
+ roles:
+ - role: common
+ tags: common
+
+ - role: mastodon
+ tags: mastodon
+
+ - role: apache_vhost
+ apache_server_name: '{{ mastodon_web_domain }}'
+ apache_server_aliases: []
+ apache_letsencrypt: yes
+ apache_redirect_to_https: yes
+ apache_document_root: '{{ mastodon_webroot }}'
+ apache_config: '{{ mastodon_apache_config }}'
+ tags: apache
diff --git a/playbooks/populate_domain.yml b/playbooks/populate_domain.yml
index acb1ec7..c118af9 100644
--- a/playbooks/populate_domain.yml
+++ b/playbooks/populate_domain.yml
@@ -24,6 +24,8 @@
attributes:
mailAlternateAddress: '{{ item.mail_aliases | default([]) }}'
jid: '{{ item.jid | default([]) }}'
+ mastodonUsername: '{{ item.mastodon_id | default([]) }}'
+ matrixUsername: '{{ item.mxid | default([]) }}'
bind_dn: uid={{ ipa_user }},{{ freeipa_user_basedn }}
bind_pw: '{{ ipa_pass }}'
server_uri: ldaps://{{ ipa_host }}
diff --git a/playbooks/site.yml b/playbooks/site.yml
index fa79f8d..48b6144 100644
--- a/playbooks/site.yml
+++ b/playbooks/site.yml
@@ -36,3 +36,4 @@
- import_playbook: xmpp.yml
- import_playbook: asterisk.yml
- import_playbook: matrix.yml
+- import_playbook: mastodon.yml
diff --git a/playbooks/webserver_public_example.yml b/playbooks/webserver_public_example.yml
index d9cb468..69a3254 100644
--- a/playbooks/webserver_public_example.yml
+++ b/playbooks/webserver_public_example.yml
@@ -11,6 +11,7 @@
apache_letsencrypt: yes
apache_document_root: /var/www/www.example.com
apache_config: |
+ Redirect permanent /.well-known/webfinger https://mastodon.example.com/.well-known/webfinger
Alias /.well-known/matrix /var/www/well-known/example.com/matrix
tags: apache
diff --git a/roles/freeipa_server/files/usr/local/share/dirsrv/schema/mastodon.ldif b/roles/freeipa_server/files/usr/local/share/dirsrv/schema/mastodon.ldif
new file mode 100644
index 0000000..f90bc66
--- /dev/null
+++ b/roles/freeipa_server/files/usr/local/share/dirsrv/schema/mastodon.ldif
@@ -0,0 +1,3 @@
+dn: cn=config
+attributetypes: ( 2.25.10508909625911985622145696820691585120.4 NAME 'mastodonUsername' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Extending FreeIPA' )
+objectclasses: ( 2.25.10508909625911985622145696820691585120.3 NAME 'mastodonUser' AUXILIARY MAY mastodonUsername X-ORIGIN 'Extending FreeIPA' )
diff --git a/roles/freeipa_server/tasks/custom_schema.yml b/roles/freeipa_server/tasks/custom_schema.yml
index 73456e3..849b524 100644
--- a/roles/freeipa_server/tasks/custom_schema.yml
+++ b/roles/freeipa_server/tasks/custom_schema.yml
@@ -11,6 +11,7 @@
loop:
- jid
- matrix
+ - mastodon
# begin JIDObject schema
@@ -109,6 +110,54 @@
when: matrixusername_index.changed
# end matrixUser schema
+# begin mastodonUser schema
+- name: check if mastodonUser exists in schema
+ shell: ldapsearch -QLLL -s base -b cn=schema objectclasses | grep -q mastodonUser
+ changed_when: no
+ failed_when: no
+ register: ldapsearch_mastodonuser
+
+- block:
+ - name: extend freeipa schema for mastodon usernames
+ command: ipa-ldap-updater --schema-file '{{ freeipa_custom_schema_dir }}/mastodon.ldif'
+
+ - name: restart httpd
+ systemd:
+ name: httpd
+ state: restarted
+ when: ldapsearch_mastodonuser.rc != 0
+
+- name: add index to mastodonUsername attribute
+ ldap_entry:
+ dn: 'cn=mastodonUsername,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config'
+ objectClass:
+ - top
+ - nsIndex
+ attributes:
+ cn: mastodonUsername
+ nsSystemIndex: false
+ nsIndexType: eq
+ bind_dn: cn=Directory Manager
+ bind_pw: '{{ freeipa_ds_password }}'
+ server_uri: ldaps://{{ ipa_host }}
+ register: mastodonusername_index
+
+- name: regenerate indexes for mastodonUsername attribute
+ ldap_entry:
+ dn: cn=mastodonusernameindex,cn=index,cn=tasks,cn=config
+ objectClass:
+ - top
+ - extensibleObject
+ attributes:
+ cn: mastodonusernameindex
+ nsInstance: userRoot
+ nsIndexAttribute: 'mastodonUsername:eq'
+ bind_dn: cn=Directory Manager
+ bind_pw: '{{ freeipa_ds_password }}'
+ server_uri: ldaps://{{ ipa_host }}
+ when: mastodonusername_index.changed
+# end mastodonUser schema
+
- name: add default user object classes
ldap_attrs:
dn: cn=ipaConfig,cn=etc,{{ freeipa_basedn }}
@@ -117,6 +166,7 @@
- mailRecipient
- JIDObject
- matrixUser
+ - mastodonUser
state: present
bind_dn: cn=Directory Manager
bind_pw: '{{ freeipa_ds_password }}'
@@ -142,6 +192,7 @@
- mailAlternateAddress
- jid
- matrixUsername
+ - mastodonUsername
action: member
state: present
diff --git a/roles/mastodon/defaults/main.yml b/roles/mastodon/defaults/main.yml
new file mode 100644
index 0000000..d9bab79
--- /dev/null
+++ b/roles/mastodon/defaults/main.yml
@@ -0,0 +1,23 @@
+mastodon_version: 4.1.2
+
+mastodon_access_group: role-mastodon-access
+mastodon_db_user: s-mastodon
+# mastodon_db_password
+mastodon_db_host: '{{ postgresql_host }}'
+mastodon_db_name: mastodon
+mastodon_ldap_host: '{{ freeipa_hosts | first }}'
+
+mastodon_domain: '{{ email_domain }}'
+mastodon_web_domain: '{{ ansible_fqdn }}'
+mastodon_email_from: 'mastodon-noreply@{{ email_domain }}'
+
+mastodon_default_locale: en
+
+mastodon_registrations: close
+
+mastodon_redis_port: 6379
+mastodon_web_port: 8008
+mastodon_streaming_port: 8009
+
+mastodon_sysaccount_username: mastodon
+#mastodon_sysaccount_password
diff --git a/roles/mastodon/handlers/main.yml b/roles/mastodon/handlers/main.yml
new file mode 100644
index 0000000..20fe804
--- /dev/null
+++ b/roles/mastodon/handlers/main.yml
@@ -0,0 +1,8 @@
+- name: restart mastodon
+ systemd:
+ name: '{{ item }}'
+ state: restarted
+ loop:
+ - mastodon-web
+ - mastodon-streaming
+ - mastodon-sidekiq
diff --git a/roles/mastodon/meta/main.yml b/roles/mastodon/meta/main.yml
new file mode 100644
index 0000000..8c0b12b
--- /dev/null
+++ b/roles/mastodon/meta/main.yml
@@ -0,0 +1,14 @@
+dependencies:
+ - role: yum
+ yum_repositories:
+ - epel
+ - rpmfusion-free
+ tags: yum
+
+ - role: freeipa_system_account
+ system_account_username: '{{ mastodon_sysaccount_username }}'
+ system_account_password: '{{ mastodon_sysaccount_password }}'
+
+ - role: redis
+ redis_port: '{{ mastodon_redis_port }}'
+ tags: redis
diff --git a/roles/mastodon/tasks/database.yml b/roles/mastodon/tasks/database.yml
new file mode 100644
index 0000000..37f6dcd
--- /dev/null
+++ b/roles/mastodon/tasks/database.yml
@@ -0,0 +1,23 @@
+- name: create database user
+ postgresql_user:
+ name: '{{ mastodon_db_user }}'
+ password: '{{ mastodon_db_password }}'
+ role_attr_flags: CREATEDB
+ state: present
+ environment:
+ PGOPTIONS: "-c password_encryption=scram-sha-256"
+ delegate_to: "{{ postgresql_inventory_host }}"
+ become: yes
+ become_user: postgres
+ register: mastodon_db_user
+
+- name: create database schema
+ command:
+ chdir: '{{ mastodon_install_dir }}'
+ cmd: 'bundle exec rake db:setup'
+ environment:
+ RAILS_ENV: production
+ SAFETY_ASSURED: 1
+ become: yes
+ become_user: '{{ mastodon_user }}'
+ when: mastodon_db_user.changed
diff --git a/roles/mastodon/tasks/freeipa.yml b/roles/mastodon/tasks/freeipa.yml
new file mode 100644
index 0000000..ee68b13
--- /dev/null
+++ b/roles/mastodon/tasks/freeipa.yml
@@ -0,0 +1,8 @@
+- name: create access group
+ ipagroup:
+ ipaadmin_principal: '{{ ipa_user }}'
+ ipaadmin_password: '{{ ipa_pass }}'
+ name: '{{ mastodon_access_group }}'
+ nonposix: yes
+ state: present
+ run_once: yes
diff --git a/roles/mastodon/tasks/main.yml b/roles/mastodon/tasks/main.yml
new file mode 100644
index 0000000..7ff23dd
--- /dev/null
+++ b/roles/mastodon/tasks/main.yml
@@ -0,0 +1,121 @@
+- name: install packages
+ dnf:
+ name: '{{ mastodon_packages }}'
+ state: present
+
+- name: add local user
+ user:
+ name: '{{ mastodon_user }}'
+ system: yes
+ home: '{{ mastodon_home }}'
+ shell: /sbin/nologin
+ create_home: no
+
+- import_tasks: freeipa.yml
+
+- name: create home directory
+ file:
+ path: '{{ mastodon_home }}'
+ owner: '{{ mastodon_user }}'
+ group: '{{ mastodon_user }}'
+ mode: 0755
+ state: directory
+
+- name: clone repo
+ git:
+ repo: '{{ mastodon_git_repo }}'
+ dest: '{{ mastodon_install_dir }}'
+ version: 'v{{ mastodon_version }}'
+ update: yes
+ force: yes
+ become: yes
+ become_user: '{{ mastodon_user }}'
+ register: mastodon_git
+
+- name: set selinux context on writeable directories
+ sefcontext:
+ target: '{{ mastodon_webroot }}(/.*)?'
+ setype: httpd_sys_content_t
+ state: present
+ register: mastodon_webroot_sefcontext
+ tags: selinux
+
+- name: apply selinux context to writeable directories
+ command: 'restorecon -R {{ mastodon_webroot }}'
+ when: mastodon_webroot_sefcontext.changed
+ tags: selinux
+
+- name: build mastodon
+ command:
+ chdir: '{{ mastodon_install_dir }}'
+ cmd: '{{ item }}'
+ loop:
+ - "bundle config deployment 'true'"
+ - "bundle config without 'development test'"
+ - 'bundle install -j{{ ansible_processor_vcpus }}'
+ - yarn install --pure-lockfile
+ become: yes
+ become_user: '{{ mastodon_user }}'
+ notify: restart mastodon
+ when: mastodon_git.changed
+
+- name: generate .env.production
+ template:
+ src: '{{ mastodon_install_dir[1:] }}/.env.production.j2'
+ dest: '{{ mastodon_install_dir }}/.env.production'
+ owner: '{{ mastodon_user }}'
+ group: '{{ mastodon_user }}'
+ mode: 0600
+ notify: restart mastodon
+
+- import_tasks: database.yml
+
+- name: precompile assets
+ command:
+ chdir: '{{ mastodon_install_dir }}'
+ cmd: 'bundle exec rake assets:precompile'
+ environment:
+ NODE_OPTIONS: --openssl-legacy-provider
+ RAILS_ENV: production
+ become: yes
+ become_user: '{{ mastodon_user }}'
+ when: mastodon_git.changed
+
+- name: create systemd units
+ template:
+ src: etc/systemd/system/{{ item }}.j2
+ dest: /etc/systemd/system/{{ item }}
+ loop:
+ - mastodon-sidekiq.service
+ - mastodon-streaming.service
+ - mastodon-web.service
+ - mastodon-cleanup.service
+ - mastodon-cleanup.timer
+ register: mastodon_systemd_units
+ notify: restart mastodon
+
+- name: reload systemd daemons
+ systemd:
+ daemon_reload: yes
+ when: mastodon_systemd_units.changed
+
+- name: start mastodon
+ systemd:
+ name: '{{ item }}'
+ enabled: yes
+ state: started
+ loop:
+ - mastodon-sidekiq.service
+ - mastodon-streaming.service
+ - mastodon-web.service
+ - mastodon-cleanup.timer
+
+- name: configure registrations
+ command:
+ chdir: '{{ mastodon_install_dir }}'
+ cmd: './bin/tootctl settings registrations {{ mastodon_registrations }}'
+ environment:
+ RAILS_ENV: production
+ become: yes
+ become_user: '{{ mastodon_user }}'
+ changed_when: no
diff --git a/roles/mastodon/templates/etc/systemd/system/mastodon-cleanup.service.j2 b/roles/mastodon/templates/etc/systemd/system/mastodon-cleanup.service.j2
new file mode 100644
index 0000000..3db1ea3
--- /dev/null
+++ b/roles/mastodon/templates/etc/systemd/system/mastodon-cleanup.service.j2
@@ -0,0 +1,49 @@
+[Unit]
+Description=mastodon-cleanup
+After=network.target
+
+[Service]
+Type=oneshot
+User={{ mastodon_user }}
+WorkingDirectory={{ mastodon_install_dir }}
+Environment="RAILS_ENV=production"
+ExecStart={{ mastodon_install_dir }}/bin/tootctl media remove
+ExecStart={{ mastodon_install_dir }}/bin/tootctl preview_cards remove
+# Proc filesystem
+ProcSubset=pid
+ProtectProc=invisible
+# Capabilities
+CapabilityBoundingSet=
+# Security
+NoNewPrivileges=true
+# Sandboxing
+ProtectSystem=strict
+PrivateTmp=true
+PrivateDevices=true
+PrivateUsers=true
+ProtectHostname=true
+ProtectKernelLogs=true
+ProtectKernelModules=true
+ProtectKernelTunables=true
+ProtectControlGroups=true
+RestrictAddressFamilies=AF_INET
+RestrictAddressFamilies=AF_INET6
+RestrictAddressFamilies=AF_NETLINK
+RestrictAddressFamilies=AF_UNIX
+RestrictNamespaces=true
+LockPersonality=true
+RestrictRealtime=true
+RestrictSUIDSGID=true
+RemoveIPC=true
+PrivateMounts=true
+ProtectClock=true
+# System Call Filtering
+SystemCallArchitectures=native
+SystemCallFilter=~@cpu-emulation @debug @ipc @mount @obsolete @privileged @setuid
+SystemCallFilter=@chown
+SystemCallFilter=pipe
+SystemCallFilter=pipe2
+ReadWritePaths={{ mastodon_install_dir }}
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/mastodon/templates/etc/systemd/system/mastodon-cleanup.timer.j2 b/roles/mastodon/templates/etc/systemd/system/mastodon-cleanup.timer.j2
new file mode 100644
index 0000000..a767551
--- /dev/null
+++ b/roles/mastodon/templates/etc/systemd/system/mastodon-cleanup.timer.j2
@@ -0,0 +1,10 @@
+[Unit]
+Description=Mastodon cleanup on calendar interval
+
+[Timer]
+OnCalendar=weekly
+AccuracySec=1h
+Persistent=true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/mastodon/templates/etc/systemd/system/mastodon-sidekiq.service.j2 b/roles/mastodon/templates/etc/systemd/system/mastodon-sidekiq.service.j2
new file mode 100644
index 0000000..9b9abfd
--- /dev/null
+++ b/roles/mastodon/templates/etc/systemd/system/mastodon-sidekiq.service.j2
@@ -0,0 +1,52 @@
+[Unit]
+Description=mastodon-sidekiq
+After=network.target
+
+[Service]
+Type=simple
+User={{ mastodon_user }}
+WorkingDirectory={{ mastodon_install_dir }}
+Environment="RAILS_ENV=production"
+Environment="DB_POOL=25"
+Environment="MALLOC_ARENA_MAX=2"
+ExecStart=/usr/bin/bundle exec sidekiq -c 25
+TimeoutSec=15
+Restart=always
+# Proc filesystem
+ProcSubset=pid
+ProtectProc=invisible
+# Capabilities
+CapabilityBoundingSet=
+# Security
+NoNewPrivileges=true
+# Sandboxing
+ProtectSystem=strict
+PrivateTmp=true
+PrivateDevices=true
+PrivateUsers=true
+ProtectHostname=true
+ProtectKernelLogs=true
+ProtectKernelModules=true
+ProtectKernelTunables=true
+ProtectControlGroups=true
+RestrictAddressFamilies=AF_INET
+RestrictAddressFamilies=AF_INET6
+RestrictAddressFamilies=AF_NETLINK
+RestrictAddressFamilies=AF_UNIX
+RestrictNamespaces=true
+LockPersonality=true
+RestrictRealtime=true
+RestrictSUIDSGID=true
+RemoveIPC=true
+PrivateMounts=true
+ProtectClock=true
+# System Call Filtering
+SystemCallArchitectures=native
+SystemCallFilter=~@cpu-emulation @debug @ipc @mount @obsolete @privileged @setuid
+SystemCallFilter=@chown
+SystemCallFilter=pipe
+SystemCallFilter=pipe2
+ReadWritePaths={{ mastodon_install_dir }}
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/mastodon/templates/etc/systemd/system/mastodon-streaming.service.j2 b/roles/mastodon/templates/etc/systemd/system/mastodon-streaming.service.j2
new file mode 100644
index 0000000..48f58ab
--- /dev/null
+++ b/roles/mastodon/templates/etc/systemd/system/mastodon-streaming.service.j2
@@ -0,0 +1,51 @@
+[Unit]
+Description=mastodon-streaming
+After=network.target
+
+[Service]
+Type=simple
+User={{ mastodon_user }}
+WorkingDirectory={{ mastodon_install_dir }}
+Environment="NODE_ENV=production"
+Environment="PORT={{ mastodon_streaming_port }}"
+Environment="STREAMING_CLUSTER_NUM=1"
+ExecStart=/usr/bin/node ./streaming
+TimeoutSec=15
+Restart=always
+# Proc filesystem
+ProcSubset=pid
+ProtectProc=invisible
+# Capabilities
+CapabilityBoundingSet=
+# Security
+NoNewPrivileges=true
+# Sandboxing
+ProtectSystem=strict
+PrivateTmp=true
+PrivateDevices=true
+PrivateUsers=true
+ProtectHostname=true
+ProtectKernelLogs=true
+ProtectKernelModules=true
+ProtectKernelTunables=true
+ProtectControlGroups=true
+RestrictAddressFamilies=AF_INET
+RestrictAddressFamilies=AF_INET6
+RestrictAddressFamilies=AF_NETLINK
+RestrictAddressFamilies=AF_UNIX
+RestrictNamespaces=true
+LockPersonality=true
+RestrictRealtime=true
+RestrictSUIDSGID=true
+RemoveIPC=true
+PrivateMounts=true
+ProtectClock=true
+# System Call Filtering
+SystemCallArchitectures=native
+SystemCallFilter=~@cpu-emulation @debug @ipc @memlock @mount @obsolete @privileged @resources @setuid
+SystemCallFilter=pipe
+SystemCallFilter=pipe2
+ReadWritePaths={{ mastodon_install_dir }}
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/mastodon/templates/etc/systemd/system/mastodon-web.service.j2 b/roles/mastodon/templates/etc/systemd/system/mastodon-web.service.j2
new file mode 100644
index 0000000..6a3fd03
--- /dev/null
+++ b/roles/mastodon/templates/etc/systemd/system/mastodon-web.service.j2
@@ -0,0 +1,52 @@
+[Unit]
+Description=mastodon-web
+After=network.target
+
+[Service]
+Type=simple
+User={{ mastodon_user }}
+WorkingDirectory={{ mastodon_install_dir }}
+Environment="RAILS_ENV=production"
+Environment="PORT={{ mastodon_web_port }}"
+ExecStart=/usr/bin/bundle exec puma -C config/puma.rb
+ExecReload=/bin/kill -SIGUSR1 $MAINPID
+TimeoutSec=15
+Restart=always
+# Proc filesystem
+ProcSubset=pid
+ProtectProc=invisible
+# Capabilities
+CapabilityBoundingSet=
+# Security
+NoNewPrivileges=true
+# Sandboxing
+ProtectSystem=strict
+PrivateTmp=true
+PrivateDevices=true
+PrivateUsers=true
+ProtectHostname=true
+ProtectKernelLogs=true
+ProtectKernelModules=true
+ProtectKernelTunables=true
+ProtectControlGroups=true
+RestrictAddressFamilies=AF_INET
+RestrictAddressFamilies=AF_INET6
+RestrictAddressFamilies=AF_NETLINK
+RestrictAddressFamilies=AF_UNIX
+RestrictNamespaces=true
+LockPersonality=true
+RestrictRealtime=true
+RestrictSUIDSGID=true
+RemoveIPC=true
+PrivateMounts=true
+ProtectClock=true
+# System Call Filtering
+SystemCallArchitectures=native
+SystemCallFilter=~@cpu-emulation @debug @ipc @mount @obsolete @privileged @setuid
+SystemCallFilter=@chown
+SystemCallFilter=pipe
+SystemCallFilter=pipe2
+ReadWritePaths={{ mastodon_install_dir }}
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/mastodon/templates/opt/mastodon/mastodon/.env.production.j2 b/roles/mastodon/templates/opt/mastodon/mastodon/.env.production.j2
new file mode 100644
index 0000000..03af34a
--- /dev/null
+++ b/roles/mastodon/templates/opt/mastodon/mastodon/.env.production.j2
@@ -0,0 +1,59 @@
+# Federation
+# ----------
+# This identifies your server and cannot be changed safely later
+# ----------
+LOCAL_DOMAIN={{ mastodon_domain }}
+WEB_DOMAIN={{ mastodon_web_domain }}
+
+DEFAULT_LOCALE={{ mastodon_default_locale }}
+
+# Redis
+# -----
+REDIS_HOST=localhost
+REDIS_PORT={{ mastodon_redis_port }}
+
+# PostgreSQL
+# ----------
+DB_SSLMODE=verify-full
+DATABASE_URL=postgresql://{{ mastodon_db_user }}:{{ mastodon_db_password}}@{{ mastodon_db_host }}/{{ mastodon_db_name }}?sslmode=verify-full&sslrootcert=/etc/pki/tls/certs/ca-bundle.crt
+
+# Secrets
+# -------
+# Make sure to use `rake secret` to generate secrets
+# -------
+SECRET_KEY_BASE={{ mastodon_secret_key_base }}
+OTP_SECRET={{ mastodon_otp_secret }}
+
+# Web Push
+# --------
+# Generate with `rake mastodon:webpush:generate_vapid_key`
+# --------
+VAPID_PRIVATE_KEY={{ mastodon_vapid_public_key }}
+VAPID_PUBLIC_KEY={{ mastodon_vapid_private_key }}
+
+# Sending mail
+# ------------
+SMTP_SERVER=localhost
+SMTP_PORT=25
+SMTP_FROM_ADDRESS={{ mastodon_email_from }}
+
+# IP and session retention
+# -----------------------
+# Make sure to modify the scheduling of ip_cleanup_scheduler in config/sidekiq.yml
+# to be less than daily if you lower IP_RETENTION_PERIOD below two days (172800).
+# -----------------------
+IP_RETENTION_PERIOD=31556952
+SESSION_RETENTION_PERIOD=31556952
+
+# LDAP
+# ----
+LDAP_ENABLED=true
+LDAP_HOST={{ mastodon_ldap_host }}
+LDAP_PORT=636
+LDAP_METHOD=simple_tls
+LDAP_BASE={{ freeipa_user_basedn }}
+LDAP_BIND_DN=uid={{ mastodon_sysaccount_username }},{{ freeipa_sysaccount_basedn }}
+LDAP_PASSWORD={{ mastodon_sysaccount_password }}
+LDAP_SEARCH_FILTER=(&(%{uid}=%{email})(memberOf=cn={{ mastodon_access_group }},{{ freeipa_group_basedn }}))
+LDAP_MAIL=mail
+LDAP_UID=mastodonUsername
diff --git a/roles/mastodon/vars/main.yml b/roles/mastodon/vars/main.yml
new file mode 100644
index 0000000..cede4a0
--- /dev/null
+++ b/roles/mastodon/vars/main.yml
@@ -0,0 +1,58 @@
+mastodon_packages:
+ - nodejs
+ - yarnpkg
+ - gcc
+ - g++
+ - libicu-devel
+ - zlib-devel
+ - openssl-devel
+ - libidn-devel
+ - ruby-devel
+ - libpq-devel
+ - git
+ - ruby
+ - ImageMagick
+ - ffmpeg
+
+mastodon_keytab: /var/lib/gssproxy/clients/{{ mastodon_user }}.keytab
+mastodon_home: /opt/mastodon
+mastodon_user: mastodon
+mastodon_install_dir: '{{ mastodon_home }}/mastodon'
+mastodon_webroot: '{{ mastodon_install_dir }}/public'
+mastodon_git_repo: https://github.com/mastodon/mastodon
+
+mastodon_apache_config: |
+ {% if mastodon_login_cidrs %}
+ <Location /auth/sign_in>
+ {% for cidr in mastodon_login_cidrs %}
+ Require ip {{ cidr }}
+ {% endfor %}
+ </Location>
+ {% endif %}
+
+ <LocationMatch "^/(assets|avatars|emoji|headers|packs|sounds|system)">
+ Header always set Cache-Control "public, max-age=31536000, immutable"
+ Require all granted
+ </LocationMatch>
+
+ ProxyPass /500.html !
+ ProxyPass /sw.js !
+ ProxyPass /robots.txt !
+ ProxyPass /manifest.json !
+ ProxyPass /browserconfig.xml !
+ ProxyPass /mask-icon.svg !
+ ProxyPass /inert.css !
+ ProxyPassMatch ^(/.*\.(png|ico)$) !
+ ProxyPassMatch ^/(assets|avatars|emoji|headers|packs|sounds|system) !
+
+ {{ apache_proxy_config }}
+ ProxyPass /api/v1/streaming ws://127.0.0.1:{{ mastodon_streaming_port }}
+ ProxyPassReverse /api/v1/streaming ws://127.0.0.1:{{ mastodon_streaming_port }}
+ ProxyPass / http://127.0.0.1:{{ mastodon_web_port }}/
+ ProxyPassReverse / http://127.0.0.1:{{ mastodon_web_port }}/
+
+ ErrorDocument 500 /500.html
+ ErrorDocument 501 /500.html
+ ErrorDocument 502 /500.html
+ ErrorDocument 503 /500.html
+ ErrorDocument 504 /500.html