commit d91f174b3127826b9b3a5e5c1694b13d914f1794 Author: Kuba Orlik Date: Sat Aug 22 19:52:25 2026 +0200 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..db55b27 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# Usage: + +See defaults/main.yml for description of config parameters + + +```yaml +- name: "Install mastodon" + hosts: czynna1 + vars: + mastodon_docker_web_port: 3008 + mastodon_docker_streaming_port: 3009 + mastodon_docker_main_storage: "/mnt/juice/mastodon-docker" + roles: + - role: mastodon-docker + tags: + - mastodon-toot + vars: + mastodon_docker_domain: "toot.eu" + mastodon_docker_user: "mastodon-toot" + mastodon_docker_max_post_length: 500 + mastodon_docker_home: "/var/www/mastodon-toot" + mastodon_docker_postgres_storage: "{{ mastodon_docker_main_storage }}/postgres" + mastodon_docker_redis_storage: "{{ mastodon_docker_main_storage }}/redis" + mastodon_docker_elasticsearch_storage: "{{ mastodon_docker_main_storage }}/elasticsearch" + mastodon_docker_system_storage: "{{ mastodon_docker_main_storage }}/system" + mastodon_docker_backup_storage: "{{ mastodon_docker_main_storage }}/backups" + mastodon_docker_cache_storage: "{{ mastodon_docker_main_storage }}/cache" + mastodon_docker_enable_elasticsearch: false + mastodon_docker_elasticsearch_port: 9200 + mastodon_docker_mastodon_secret: + mastodon_docker_vapid_public: + mastodon_docker_vapid_private: + mastodon_docker_ar_encryption_deterministic_key: + mastodon_docker_ar_encryption_derivation_salt: + mastodon_docker_ar_encryption_primary_key: + mastodon_docker_smtp: + server: example.com + port: 587 + login: login + password: + from: mastodon@example.com + + - role: sealcode.nginx.proxies + tags: + - mastodon-toot + vars: + - domain: "toot.eu" + site_name: "mastodon-toot" + upstream: "http://127.0.0.1:{{mastodon_docker_web_port}}" + websocket_endpoints: + - url: "/api/v1/streaming" + upstream: "http://127.0.0.1:{{ mastodon_docker_streaming_port }}" +``` diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..a68619a --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,26 @@ +mastodon_docker_domain: "example.com" +mastodon_docker_user: "mastodon-docker" +mastodon_docker_max_post_length: 500 +mastodon_docker_home: "/var/www/mastodon-docker" +mastodon_docker_postgres_storage: "/mnt/juice/mastodon-docker/postgres" +mastodon_docker_redis_storage: "/mnt/juice/mastodon-docker/redis" +mastodon_docker_elasticsearch_storage: "/mnt/juice/mastodon-docker/elasticsearch" +mastodon_docker_system_storage: "/mnt/juice/mastodon-docker/system" +mastodon_docker_backup_storage: "/mnt/juice/mastodon-docker/backup" +mastodon_docker_cache_storage: "/mnt/juice/mastodon-docker/cache" +mastodon_docker_enable_elasticsearch: false +mastodon_docker_mastodon_secret: undefined # can be generated with a password manager +mastodon_docker_vapid_public: undefined # generate with npx web-push generate-vapid-keys +mastodon_docker_vapid_private: undefined # generate with npx web-push generate-vapid-keys +mastodon_docker_ar_encryption_deterministic_key: undefined # generate with openssl rand -hex 32 +mastodon_docker_ar_encryption_derivation_salt: undefined # generate with openssl rand -hex 32 +mastodon_docker_ar_encryption_primary_key: undefined # generate with openssl rand -hex 32 +mastodon_docker_smtp: + server: example.com + port: 587 + login: login + password: somesecret + from: mastodon@example.com +mastodon_docker_web_port: 3008 +mastodon_docker_streaming_port: 3009 +mastodon_docker_media_cleanup_days: 3 diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..fa0a81d --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,17 @@ +--- +galaxy_info: + author: kuba + description: upgrade and reboot + license: MIT + min_ansible_version: "2.12" + platforms: + - name: Debian + versions: + - bullseye + - bookworm + - name: Ubuntu + versions: + - focal + - jammy + +dependencies: [] diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..3de360e --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,108 @@ +- name: create Mastodon Docker user + ansible.builtin.user: + name: "{{ mastodon_docker_user }}" + shell: /bin/bash + home: "{{ mastodon_docker_home }}" + create_home: true + state: present + +- name: Create directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0777" + loop: + - "{{ mastodon_docker_postgres_storage }}" + - "{{ mastodon_docker_redis_storage }}" + - "{{ mastodon_docker_elasticsearch_storage }}" + - "{{ mastodon_docker_system_storage }}" + - "{{ mastodon_docker_backup_storage }}" + - "{{ mastodon_docker_cache_storage }}" + +- name: Make sure data directories are backed up + lineinfile: + path: "/backup-dirs" + line: "{{ item }}" + loop: + - "{{ mastodon_docker_postgres_storage }}" + - "{{ mastodon_docker_redis_storage }}" + - "{{ mastodon_docker_elasticsearch_storage }}" + - "{{ mastodon_docker_system_storage }}" + - "{{ mastodon_docker_backup_storage }}" + # cache storage intentionally omitted + +- name: Copy the docker compose file to server + template: + src: "docker-compose.yml.j2" + dest: "{{ mastodon_docker_home }}/docker-compose.yml" + register: compose_file + +- name: Copy the Dockerfile to server + template: + src: "Dockerfile" + dest: "{{ mastodon_docker_home }}/Dockerfile" + register: compose_file + +- name: Copy the env file to server + template: + src: "env.production.j2" + dest: "{{ mastodon_docker_home }}/env" + register: env_file + +- name: Copy the tootctl file to server + template: + src: "tootctl" + dest: "{{ mastodon_docker_home }}/tootctl" + mode: "0770" + +- name: Copy the bundle file to server + template: + src: "bundle" + dest: "{{ mastodon_docker_home }}/bundle" + mode: "0770" + +- name: "Stop mastodon_docker services" + community.docker.docker_compose_v2: + state: "absent" + project_src: "{{ mastodon_docker_home }}" + when: compose_file.changed or env_file.changed + +- name: run the setup command + command: + cmd: "bash -c 'docker compose run --rm web bundle exec rails db:setup && touch db_setup.mark'" + chdir: "{{ mastodon_docker_home }}" + creates: "db_setup.mark" + +- name: "docker compose up -d" + community.docker.docker_compose_v2: + state: "present" + build: "always" + pull: missing + project_src: "{{ mastodon_docker_home }}" + +- name: Add Mastodon Postgres backup cron + cron: + name: "Mastodon Postgres backup: {{ mastodon_docker_domain }}" + user: root + minute: "0" + hour: "3" + job: > + /bin/sh -c '/usr/bin/docker compose --project-directory {{mastodon_docker_home}} exec -T db pg_dump -U mastodon mastodon_production > {{mastodon_docker_backup_storage}}/mastodon_backup.sql' + +- name: Add Mastodon media attachments cache cleanup + cron: + name: "Mastodon media attachments cleanup backup: {{ mastodon_docker_domain }}" + user: root + minute: "0" + hour: "1" + job: > + /bin/sh -c '/usr/bin/docker compose --project-directory {{mastodon_docker_home}} exec web bin/tootctl media remove --days {{ mastodon_docker_media_cleanup_days }}' + +- name: Add Mastodon media headers cache cleanup + cron: + name: "Mastodon media headers cleanup backup: {{ mastodon_docker_domain }}" + user: root + minute: "30" + hour: "1" + job: > + /bin/sh -c '/usr/bin/docker compose --project-directory {{mastodon_docker_home}} exec web bin/tootctl media remove --days {{ mastodon_docker_media_cleanup_days }} --remove-headers' diff --git a/templates/Dockerfile b/templates/Dockerfile new file mode 100644 index 0000000..4abb6f8 --- /dev/null +++ b/templates/Dockerfile @@ -0,0 +1,8 @@ +FROM ghcr.io/mastodon/mastodon:v4.5.9 + +USER root + +RUN sed -i "s/MAX_CHARS = 500/MAX_CHARS = ENV.fetch('MAX_TOOT_CHARS', 500).to_i/" \ + app/validators/status_length_validator.rb + +USER mastodon diff --git a/templates/bundle b/templates/bundle new file mode 100644 index 0000000..95c1927 --- /dev/null +++ b/templates/bundle @@ -0,0 +1,2 @@ +#!/bin/bash +docker compose run --rm web bundle "$@" diff --git a/templates/docker-compose.yml.j2 b/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..be4a7fa --- /dev/null +++ b/templates/docker-compose.yml.j2 @@ -0,0 +1,124 @@ +# From https://github.com/mastodon/mastodon/blob/main/docker-compose.yml + +services: + db: + restart: always + image: postgres:14-alpine + shm_size: 256mb + networks: + - internal_network + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + volumes: + - "{{ mastodon_docker_postgres_storage }}:/var/lib/postgresql/data" + environment: + - "POSTGRES_HOST_AUTH_METHOD=trust" + - "POSTGRES_USER=mastodon" + - "POSTGRES_DB=mastodon_production" + redis: + restart: always + image: redis:7-alpine + networks: + - internal_network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + volumes: + - "{{ mastodon_docker_redis_storage }}:/data" + +{% if mastodon_docker_enable_elasticsearch %} + es: + restart: always + image: docker.elastic.co/elasticsearch/elasticsearch:7.17.29 + 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" + networks: + - external_network + - internal_network + healthcheck: + test: + [ + "CMD-SHELL", + "curl --silent --fail localhost:9200/_cluster/health || exit 1", + ] + volumes: + - "{{mastodon_docker_elasticsearch_storage}}:/usr/share/elasticsearch/data" + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 +{% endif %} + + web: + build: . + restart: always + env_file: env + command: bundle exec puma -C config/puma.rb + networks: + - external_network + - internal_network + healthcheck: + # prettier-ignore + test: ['CMD-SHELL',"curl -s --noproxy localhost localhost:3000/health | grep -q 'OK' || exit 1"] + ports: + - "127.0.0.1:{{ mastodon_docker_web_port }}:3000" + depends_on: + - db + - redis +{% if mastodon_docker_enable_elasticsearch %} + - es +{% endif %} + volumes: + - "{{ mastodon_docker_system_storage }}:/mastodon/public/system" + - "{{ mastodon_docker_cache_storage }}:/opt/mastodon/public/system/cache" + + streaming: + image: ghcr.io/mastodon/mastodon-streaming:v4.5.9 + restart: always + env_file: env + command: node ./streaming/index.js + networks: + - external_network + - internal_network + healthcheck: + # prettier-ignore + test: ['CMD-SHELL', "curl -s --noproxy localhost localhost:4000/api/v1/streaming/health | grep -q 'OK' || exit 1"] + ports: + - "127.0.0.1:{{ mastodon_docker_streaming_port }}:4000" + depends_on: + - db + - redis + + sidekiq: + image: ghcr.io/mastodon/mastodon:v4.5.9 + restart: always + env_file: env + command: bundle exec sidekiq + depends_on: + - db + - redis + networks: + - external_network + - internal_network + volumes: + - "{{ mastodon_docker_system_storage }}:/mastodon/public/system" + - "{{ mastodon_docker_cache_storage }}:/opt/mastodon/public/system/cache" + healthcheck: + test: ["CMD-SHELL", "ps aux | grep '[s]idekiq\ 8' || false"] + + +networks: + external_network: + internal_network: + internal: true diff --git a/templates/env.production.j2 b/templates/env.production.j2 new file mode 100644 index 0000000..423fbb8 --- /dev/null +++ b/templates/env.production.j2 @@ -0,0 +1,75 @@ +MAX_TOOT_CHARS={{mastodon_docker_max_post_length}} +LOCAL_DOMAIN={{mastodon_docker_domain}} + +# Redis +# ----- +REDIS_HOST=redis +REDIS_PORT=6379 + +# PostgreSQL +# ---------- +DB_HOST=db +DB_USER=mastodon +DB_NAME=mastodon_production +DB_PASS= +DB_PORT=5432 + +# Elasticsearch (optional) +# ------------------------ +ES_ENABLED={{mastodon_docker_enable_elasticsearch}} +ES_HOST=es +ES_PORT=9200 +# Authentication for ES (optional) +ES_USER=elastic +ES_PASS=password + +# Secrets +# ------- +# Make sure to use `bundle exec rails secret` to generate secrets +# ------- +SECRET_KEY_BASE={{ mastodon_docker_mastodon_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_docker_ar_encryption_deterministic_key}} +ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT={{mastodon_docker_ar_encryption_derivation_salt}} +ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY={{mastodon_docker_ar_encryption_primary_key}} + +# Web Push +# -------- +# Generate with `bundle exec rails mastodon:webpush:generate_vapid_key` +# -------- +VAPID_PRIVATE_KEY={{mastodon_docker_vapid_public}} +VAPID_PUBLIC_KEY={{mastodon_docker_vapid_private}} + +# Sending mail +# ------------ +SMTP_SERVER={{mastodon_docker_smtp.server}} +SMTP_PORT=587{{mastodon_docker_smtp.port}} +SMTP_LOGIN={{mastodon_docker_smtp.login}} +SMTP_PASSWORD={{mastodon_docker_smtp.password}} +SMTP_FROM_ADDRESS={{mastodon_docker_smtp.from}} + +# File storage (optional) +# ----------------------- +S3_ENABLED=false +S3_BUCKET=files.example.com +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +S3_ALIAS_HOST=files.example.com + +# Optional list of hosts that are allowed to serve media for your instance +# EXTRA_MEDIA_HOSTS=https://data.example1.com,https://data.example2.com + +# 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 \ No newline at end of file diff --git a/templates/tootctl b/templates/tootctl new file mode 100644 index 0000000..c0f12fe --- /dev/null +++ b/templates/tootctl @@ -0,0 +1,2 @@ +#!/bin/bash +docker compose run --rm web bin/tootctl "$@" \ No newline at end of file