diff --git a/inventory/ghp/sample/group_vars/all/all.yaml b/inventory/ghp/sample/group_vars/all/all.yaml index e58e5b9..b185dfd 100644 --- a/inventory/ghp/sample/group_vars/all/all.yaml +++ b/inventory/ghp/sample/group_vars/all/all.yaml @@ -117,3 +117,12 @@ adguard_loadbalancer_ip: "192.168.250.6" #adguard_config_storage: "nfs-ssd" #adguard_work_size: "10Gi" #adguard_work_storage: "nfs-ssd" + +### Mastodon ### +mastodon_enabled: false +mastodon_publish: true +#mastodon_enable_elasticsearch: true +#mastodon_assets_size: "10Gi" +#mastodon_assets_storage: "nfs-ssd" +#mastodon_system_size: "100Gi" +#mastodon_system_storage: "nfs-hdd" diff --git a/inventory/ghp/sample/group_vars/ddclient.yaml b/inventory/ghp/sample/group_vars/ddclient.yaml index 9bf8756..3f12572 100644 --- a/inventory/ghp/sample/group_vars/ddclient.yaml +++ b/inventory/ghp/sample/group_vars/ddclient.yaml @@ -28,11 +28,12 @@ ddclient_hosts: - "{% if gitea_publish_web | default(false) %}{{ gitea_short_name | default('gitea') }}.{{ domain }}{% else %}omitme{% endif %}" - "{% if bitwarden_publish | default(false) %}{{ bitwarden_short_name | default('bitwarden') }}.{{ domain }}{% else %}omitme{% endif %}" - "{% if playmaker_publish | default(false) %}{{ playmaker_short_name | default('playmaker') }}.{{ domain }}{% else %}omitme{% endif %}" - - "{% if pipyserver_publish | default(false) %}{{ pipyserver_short_name | default('pip') }}.{{ domain }}{% else %}omitme{% endif %}" + - "{% if pypiserver_publish | default(false) %}{{ pypiserver_short_name | default('pip') }}.{{ domain }}{% else %}omitme{% endif %}" - "{% if wikijs_publish | default(false) %}{{ wikijs_short_name | default('wikijs') }}.{{ domain }}{% else %}omitme{% endif %}" - "{% if chartmuseum_publish | default(false) %}{{ chartsmuseum_short_name | default('charts') }}.{{ domain }}{% else %}omitme{% endif %}" - "{% if registry_publish | default(false) %}{{ registry_short_name | default('registry') }}.{{ domain }}{% else %}omitme{% endif %}" - "{% if peertube_publish | default(false) %}{{ peertube_short_name | default('peertube') }}.{{ domain }}{% else %}omitme{% endif %}" + - "{% if mastodon_publish | default(false) %}{{ mastodon_short_name | default('mastodon') }}.{{ domain }}{% else %}omitme{% endif %}" - "{% if harbor_publish | default(false) %}{{ harbor_short_name | default('harbor') }}.{{ domain }}{% else %}omitme{% endif %}" - "{% if roundcube_publish | default(false) %}{{ roundcube_short_name | default('webmail') }}.{{ domain }}{% else %}omitme{% endif %}" - "{{ harbor_readonly_ingress | default('omitme') }}" diff --git a/inventory/ghp/sample/group_vars/k8s/mastodon.yaml b/inventory/ghp/sample/group_vars/k8s/mastodon.yaml new file mode 100644 index 0000000..e3c099a --- /dev/null +++ b/inventory/ghp/sample/group_vars/k8s/mastodon.yaml @@ -0,0 +1 @@ +mastodon_values: {} diff --git a/inventory/ghp/sample/group_vars/web_proxy.yaml b/inventory/ghp/sample/group_vars/web_proxy.yaml index 8638021..9bc0a53 100644 --- a/inventory/ghp/sample/group_vars/web_proxy.yaml +++ b/inventory/ghp/sample/group_vars/web_proxy.yaml @@ -50,8 +50,8 @@ nginx: {% if playmaker_publish %} {{ playmaker_short_name | default('playmaker') }}.{{ domain }} https_{{ namespace }}; {% endif %} - {% if pipyserver_publish %} - {{ pipyserver_short_name | default('pip') }}.{{ domain }} https_{{ namespace }}; + {% if pypiserver_publish %} + {{ pypiserver_short_name | default('pip') }}.{{ domain }} https_{{ namespace }}; {% endif %} {% if bitwarden_publish %} {{ bitwarden_short_name | default('bitwarden') }}.{{ domain }} https_{{ namespace }}; @@ -74,6 +74,9 @@ nginx: {% if peertube_publish %} {{ peertube_short_name | default('peertube') }}.{{ domain }} https_{{ namespace }}; {% endif %} + {% if mastodon_publish %} + {{ mastodon_short_name | default('mastodon') }}.{{ domain }} https_{{ namespace }}; + {% endif %} {% if roundcube_publish %} {{ roundcube_short_name | default('webmail') }}.{{ domain }} https_{{ namespace }}; {% endif %} diff --git a/playbooks/ghp/mastodon.yaml b/playbooks/ghp/mastodon.yaml new file mode 100644 index 0000000..8be464b --- /dev/null +++ b/playbooks/ghp/mastodon.yaml @@ -0,0 +1,5 @@ +--- +- hosts: k8s + connection: local + roles: + - mastodon diff --git a/playbooks/ghp/peertube.yaml b/playbooks/ghp/peertube.yaml new file mode 100644 index 0000000..4093d23 --- /dev/null +++ b/playbooks/ghp/peertube.yaml @@ -0,0 +1,5 @@ +--- +- hosts: k8s + connection: local + roles: + - peertube diff --git a/playbooks/ghp/user-apps.yaml b/playbooks/ghp/user-apps.yaml index fd0606c..4ac85ca 100644 --- a/playbooks/ghp/user-apps.yaml +++ b/playbooks/ghp/user-apps.yaml @@ -61,3 +61,9 @@ name: adguard-home when: adguard_enabled | default(false) tags: adguard + + - name: Deploy Mastodon + import_role: + name: mastodon + when: mastodon_enabled | default(false) + tags: mastodon diff --git a/roles/ddclient/defaults/main.yml b/roles/ddclient/defaults/main.yml index 236f0a2..43669fb 100644 --- a/roles/ddclient/defaults/main.yml +++ b/roles/ddclient/defaults/main.yml @@ -1,3 +1,24 @@ dockerize: false namespace: ddclient ddclient_image_tag: v3.9.1-ls45 + +harbor_readonly_ingress: false +registry_readonly_ingress: false +wikijs_readonly_ingress: false +chartmuseum_readonly_ingress: false +registry_publish: false +chartmuseum_publish: false +harbor_publish: false +roundcube_publish: false +nextcloud_publish: true +bitwarden_publish: false +gitea_publish_web: false +gitea_publish_ssh: false +drone_publish: false +wikijs_publish: false +playmaker_publish: false +pypiserver_publish: false +peertube_publish: false +adguard_publish: false +mastodon_publish: false + diff --git a/roles/mastodon/defaults/main.yaml b/roles/mastodon/defaults/main.yaml new file mode 100644 index 0000000..3812409 --- /dev/null +++ b/roles/mastodon/defaults/main.yaml @@ -0,0 +1,137 @@ +mastodon_enabled: false +mastodon_publish: true +mastodon_use_external_db: true +mastodon_short_name: "mastodon" +mastodon_enable_elasticsearch: true +mastodon_default_values: + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + kubernetes.io/ingress.class: "{{ external_ingress_class if mastodon_publish else internal_ingress_class }}" + kubernetes.io/tls-acme: "true" + hostname: "{{ mastodon_short_name }}.{{ domain }}" + tls: + - secretName: "{{ mastodon_short_name }}.{{ domain }}-tls" + hosts: + - "{{ mastodon_short_name }}.{{ domain }}" + + # create an initial administrator user; the password is autogenerated and will + # have to be reset + createAdmin: + enabled: true + username: mastodon + password: "{{ mastodon_admin_pass | default(mastodon_admin_password) }}" + email: "admin@{{ domain }}" + + # available locales: https://github.com/tootsuite/mastodon/blob/master/config/application.rb#L43 + locale: en + + cron: + # run `tootctl media remove` every week + removeMedia: + enabled: true + schedule: "0 0 * * 0" + + application: + web: + port: 3000 + streaming: + port: 4000 + # this should be set manually since os.cpus() returns the number of CPUs on + # the node running the pod, which is unrelated to the resources allocated to + # the pod by k8s + workers: 2 + sidekiq: + concurrency: 25 + + # these must be set manually; autogenerated keys are rotated on each upgrade + secrets: + secret_key_base: "{{ mastodon_vapid_public_key_base64 | hash('sha256') }}" + otp_secret: "{{ mastodon_vapid_public_key_base64 | hash('sha256') | hash('sha256') }}" + vapid: + private_key: "{{ mastodon_vapid_private_key_base64 | b64decode }}" + public_key: "{{ mastodon_vapid_public_key_base64 | b64decode }}" + + smtp: + auth_method: login + ca_file: + delivery_method: smtp + domain: "{{ domain }}" + enable_starttls_auto: false + from_address: "mastodon@{{ domain }}" + login: mastodon + openssl_verify_mode: false + password: "{{ mastodon_ldap_pass | default(mastodon_ldap_password) }}" + port: 465 + reply_to: "mastodon@{{ domain }}" + server: "{{ mail_short_name | default('mail') }}.{{ domain }}" + tls: true + + elasticsearch: + # `false` will disable full-text search + # + # if you enable ES after the initial install, you will need to manually run + # RAILS_ENV=production bundle exec rake chewy:sync + # (https://docs.joinmastodon.org/admin/optional/elasticsearch/) + enabled: "{{ mastodon_enable_elasticsearch }}" + master: + name: master + ## Number of master-eligible node(s) replicas to deploy + ## + replicas: 1 + coordinating: + ## Number of coordinating-only node(s) replicas to deploy + ## + replicas: 1 + data: + name: data + ## Number of data node(s) replicas to deploy + ## + replicas: 1 + + + # https://github.com/bitnami/charts/tree/master/bitnami/postgresql#parameters + postgresql: + # Disable for external PostgreSQL + enabled: false + # Set for external PostgreSQL + # postgresqlHost: postgresql.local + postgresqlHost: "{{ namespace }}-postgres.{{ postgres_db_namespace | default(namespace) }}.svc.cluster.local" + postgresqlDatabase: mastodon + # you must set a password; the password generated by the postgresql chart will + # be rotated on each upgrade: + # https://github.com/bitnami/charts/tree/master/bitnami/postgresql#upgrade + postgresqlUsername: "{{ mastodon_db_username }}" + postgresqlPassword: "{{ mastodon_db_password }}" + + # https://github.com/bitnami/charts/tree/master/bitnami/redis#parameters + redis: + enabled: true + usePassword: true + # you must set a password; the password generated by the redis chart will be + # rotated on each upgrade: + password: "{{ mastodon_vapid_public_key_base64 | hash('md5') }}" + cluster: + enabled: false + + persistence: + assets: + # ReadWriteOnce is more widely supported than ReadWriteMany, but limits + # scalability, since it requires the Rails and Sidekiq pods to run on the + # same node. + storageClassName: "{{ mastodon_assets_storage | default('nfs-ssd') }}" + accessMode: "{{ mastodon_assets_storage_mode | default('ReadWriteMany') }}" + resources: + requests: + storage: "{{ mastodon_assets_size | default('10Gi') }}" + system: + storageClassName: "{{ mastodon_system_storage | default('nfs-hdd') }}" + accessMode: "{{ mastodon_system_storage_mode | default('ReadWriteMany') }}" + resources: + requests: + storage: "{{ mastodon_system_size | default('100Gi') }}" + diff --git a/roles/mastodon/tasks/main.yaml b/roles/mastodon/tasks/main.yaml new file mode 100644 index 0000000..3142ab7 --- /dev/null +++ b/roles/mastodon/tasks/main.yaml @@ -0,0 +1,17 @@ +- name: Import secret.yaml to obtain secrets + include_tasks: secrets.yaml + when: + - mastodon_use_external_db + - postgres_enabled is defined and postgres_enabled + +- set_fact: + mastodon_combined_values: "{{ mastodon_default_values | combine(mastodon_values, recursive=true) }}" + +- name: Deploy Mastodon + community.kubernetes.helm: + create_namespace: true + release_namespace: "{{ mastodon_namespace | default(namespace) }}" + release_name: "{{ mastodon_name | default('mastodon') }}" + chart_ref: "{{ mastodon_chart | default('ghp/mastodon') }}" + chart_version: "{{ mastodon_version | default(omit) }}" + release_values: "{{ mastodon_combined_values | from_yaml }}" diff --git a/roles/mastodon/tasks/secrets.yaml b/roles/mastodon/tasks/secrets.yaml new file mode 100644 index 0000000..d1b6ca8 --- /dev/null +++ b/roles/mastodon/tasks/secrets.yaml @@ -0,0 +1,25 @@ +- block: + - name: Set DB namespace for secret lookup + set_fact: + db_namespace: "{{ mastodon_db_namespace | default(postgres_db_namespace) | default(postgres_namespace) | default(postgres_operator_namespace) | default(namespace) }}" + + - name: Set DB secret name for lookup + set_fact: + db_secret_name: "mastodon.{{ postgres_db_team | default(namespace) }}-postgres.credentials.postgresql.acid.zalan.do" + + - name: Lookup Mastodon DB secret + set_fact: + mastodon_db_secret: "{{ lookup('k8s', kind='Secret', namespace=db_namespace, resource_name=db_secret_name) }}" + + - debug: + msg: "{{ mastodon_db_secret }}" + verbosity: 2 + + - name: Set Mastodon DB username + set_fact: + mastodon_db_username: "{{ mastodon_db_secret.data.username | b64decode }}" + + - name: Set Mastodon DB password + set_fact: + mastodon_db_password: "{{ mastodon_db_secret.data.password | b64decode }}" + diff --git a/roles/nginx/defaults/main.yml b/roles/nginx/defaults/main.yml index ee286d8..083e4e0 100644 --- a/roles/nginx/defaults/main.yml +++ b/roles/nginx/defaults/main.yml @@ -2,3 +2,18 @@ harbor_readonly_ingress: false registry_readonly_ingress: false wikijs_readonly_ingress: false chartmuseum_readonly_ingress: false +registry_publish: false +chartmuseum_publish: false +harbor_publish: false +roundcube_publish: false +nextcloud_publish: true +bitwarden_publish: false +gitea_publish_web: false +gitea_publish_ssh: false +drone_publish: false +wikijs_publish: false +playmaker_publish: false +pypiserver_publish: false +peertube_publish: false +adguard_publish: false +mastodon_publish: false diff --git a/roles/openldap/defaults/main.yaml b/roles/openldap/defaults/main.yaml index cc492b1..0611964 100644 --- a/roles/openldap/defaults/main.yaml +++ b/roles/openldap/defaults/main.yaml @@ -104,14 +104,14 @@ openldap_default_values: changetype: add uid: admin cn: admin - sn: 3 + sn: 4 objectClass: top objectClass: posixAccount objectClass: inetOrgPerson loginShell: /bin/bash homeDirectory: /home/admin - uidNumber: 14583103 - gidNumber: 14564103 + uidNumber: 14583104 + gidNumber: 14564104 userPassword: {{ openldap_admin_pbkdf2_sha512_hash }} gecos: Admin user @@ -119,14 +119,14 @@ openldap_default_values: changetype: add uid: systemuser cn: systemuser - sn: 4 + sn: 5 objectClass: top objectClass: posixAccount objectClass: inetOrgPerson loginShell: /bin/bash homeDirectory: /home/systemuser - uidNumber: 14583104 - gidNumber: 14564104 + uidNumber: 14583105 + gidNumber: 14564105 userPassword: {{ systemuser_pbkdf2_sha512_hash }} mail: systemuser@{{ domain }} gecos: System user @@ -225,6 +225,22 @@ openldap_default_values: userPassword: {{ peertube_ldap_pbkdf2_sha512_hash }} mail: peertube@{{ domain }} gecos: PeerTube user + + dn: uid=mastodon,ou=users,{{ openldap_domain }} + changetype: add + uid: mastodon + cn: mastodon + sn: 12 + objectClass: top + objectClass: posixAccount + objectClass: inetOrgPerson + loginShell: /bin/bash + homeDirectory: /home/mastodon + uidNumber: 14583112 + gidNumber: 14564112 + userPassword: {{ mastodon_ldap_pbkdf2_sha512_hash }} + mail: mastodon@{{ domain }} + gecos: Mastodon user dn: cn=admin,ou=groups,{{ openldap_domain }} changetype: add @@ -246,6 +262,7 @@ openldap_default_values: uniqueMember: uid=gitea,ou=users,{{ openldap_domain }} uniqueMember: uid=wikijs,ou=users,{{ openldap_domain }} uniqueMember: uid=peertube,ou=users,{{ openldap_domain }} + uniqueMember: uid=mastodon,ou=users,{{ openldap_domain }} dn: cn=users,ou=groups,{{ openldap_domain }} changetype: add diff --git a/roles/postgres/defaults/main.yaml b/roles/postgres/defaults/main.yaml index efb53c5..db413fc 100644 --- a/roles/postgres/defaults/main.yaml +++ b/roles/postgres/defaults/main.yaml @@ -67,6 +67,7 @@ postgres_db_definitions: nextcloud: [] roundcube: [] harbor: [] + mastodon: [] databases: gitea: gitea drone: drone @@ -78,6 +79,7 @@ postgres_db_definitions: harbor_clair: harbor harbor_notary_server: harbor harbor_notary_signer: harbor + mastodon: mastodon preparedDatabases: peertube: defaultUsers: true diff --git a/roles/pwgen/defaults/main.yaml b/roles/pwgen/defaults/main.yaml index 700cb69..ff896e9 100644 --- a/roles/pwgen/defaults/main.yaml +++ b/roles/pwgen/defaults/main.yaml @@ -12,6 +12,8 @@ default_accounts: - { name: chartmuseum_admin } - { name: peertube_ldap } - { name: peertube_admin } + - { name: mastodon_admin } + - { name: mastodon_ldap } - { name: harbor_admin } - { name: systemuser } @@ -19,3 +21,6 @@ htpasswd_accounts: - { name: pypiserver_admin } - { name: adguard_admin } - { name: harbor_registry_user } + +vapid_keys: + - { name: mastodon } diff --git a/roles/pwgen/tasks/main.yaml b/roles/pwgen/tasks/main.yaml index 2a29d27..ff9153c 100644 --- a/roles/pwgen/tasks/main.yaml +++ b/roles/pwgen/tasks/main.yaml @@ -55,3 +55,6 @@ - include_tasks: tsig.yaml - include_tasks: dkim.yaml + +- include_tasks: vapid.yaml + loop: "{{ vapid_keys }}" diff --git a/roles/pwgen/tasks/vapid.yaml b/roles/pwgen/tasks/vapid.yaml new file mode 100644 index 0000000..321d7b6 --- /dev/null +++ b/roles/pwgen/tasks/vapid.yaml @@ -0,0 +1,47 @@ +- name: Test if VAPID private key exists + shell: grep -c "^{{ item.name }}_vapid_private_key_base64" "{{ inventory_dir }}/group_vars/all/passwords.yaml" || true + register: vapid_private_key_test_grep + +- name: Test if VAPID public key exists + shell: grep -c "^{{ item.name }}_vapid_public_key_base64" "{{ inventory_dir }}/group_vars/all/passwords.yaml" || true + register: vapid_public_key_test_grep + +- name: Create VAPID keys + docker_container: + name: vapid + image: "{{ docker_registry }}/pwgen" + cleanup: true + detach: false + container_default_behavior: no_defaults + command: "/vapid" + register: vapid_container_output + when: vapid_private_key_test_grep.stdout == '0' or vapid_public_key_test_grep.stdout == '0' + +- name: Set VAPID keys fact + set_fact: + vapid_keys: "{{ vapid_container_output.ansible_facts.docker_container.Output | from_yaml }}" + when: vapid_private_key_test_grep.stdout == '0' or vapid_public_key_test_grep.stdout == '0' + +- name: Show VAPID private key + debug: + msg: "vapid private key: {{ vapid_keys['vapidPrivateKey'] }}" + verbosity: 2 + when: vapid_private_key_test_grep.stdout == '0' + +- name: Show VAPID public key + debug: + msg: "vapid public key: {{ vapid_keys['vapidPublicKey'] }}" + verbosity: 2 + when: vapid_public_key_test_grep.stdout == '0' + +- name: Write VAPID private key + lineinfile: + path: "{{ inventory_dir }}/group_vars/all/passwords.yaml" + line: "{{ item.name }}_vapid_private_key_base64: \"{{ vapid_keys['vapidPrivateKey'] | b64encode }}\"" + when: vapid_private_key_test_grep.stdout == '0' + +- name: Write VAPID public key + lineinfile: + path: "{{ inventory_dir }}/group_vars/all/passwords.yaml" + line: "{{ item.name }}_vapid_public_key_base64: \"{{ vapid_keys['vapidPublicKey'] | b64encode }}\"" + when: vapid_public_key_test_grep.stdout == '0'