diff --git a/os_vars/debian.yml b/os_vars/debian.yml index 28ad247..fc36047 100644 --- a/os_vars/debian.yml +++ b/os_vars/debian.yml @@ -1 +1,14 @@ -snmp-user: Debian-snmp \ No newline at end of file +snmp-user: Debian-snmp + +redis: + packages: + - package: "redis-server" + - package: "redis-tools" + +postgres: + packages: + - package: "libpq-dev" + - package: "postgresql" + - package: "postgresql-contrib" + - package: "python3-psycopg2" + - package: "sudo" \ No newline at end of file diff --git a/os_vars/ubuntu.yml b/os_vars/ubuntu.yml index 28ad247..fc36047 100644 --- a/os_vars/ubuntu.yml +++ b/os_vars/ubuntu.yml @@ -1 +1,14 @@ -snmp-user: Debian-snmp \ No newline at end of file +snmp-user: Debian-snmp + +redis: + packages: + - package: "redis-server" + - package: "redis-tools" + +postgres: + packages: + - package: "libpq-dev" + - package: "postgresql" + - package: "postgresql-contrib" + - package: "python3-psycopg2" + - package: "sudo" \ No newline at end of file diff --git a/roles/database/tasks/install_postgres.yml b/roles/database/tasks/install_postgres.yml new file mode 100644 index 0000000..89caede --- /dev/null +++ b/roles/database/tasks/install_postgres.yml @@ -0,0 +1,9 @@ +- name: database | postgres | install postgres packages + package: + name: "{{ item.package }}" + update_cache: yes + cache_valid_time: 3600 + state: latest + install_recommends: no + become: yes + with_items: "{{ postgres.packages }}" \ No newline at end of file diff --git a/roles/database/tasks/install_redis.yml b/roles/database/tasks/install_redis.yml new file mode 100644 index 0000000..eba148a --- /dev/null +++ b/roles/database/tasks/install_redis.yml @@ -0,0 +1,9 @@ +- name: database | redis | install redis packages + package: + name: "{{ item.package }}" + update_cache: yes + cache_valid_time: 3600 + state: latest + install_recommends: no + become: yes + with_items: "{{ redis.packages }}" \ No newline at end of file diff --git a/roles/database/tasks/main.yml b/roles/database/tasks/main.yml new file mode 100644 index 0000000..ceab30e --- /dev/null +++ b/roles/database/tasks/main.yml @@ -0,0 +1,14 @@ +# Load distro-specific variables +- include_vars: "{{ ansible_distribution }}.yml" + tags: always + +- block: + - debug: + msg: Debug + # install software + - import_tasks: install_mysql.yml + when: mysql == true + - import_tasks: install_postgres.yml + when: postgres == true + - import_tasks: install_redis.yml + when: redis == true \ No newline at end of file diff --git a/roles/mastodon/tasks/main.yml b/roles/mastodon/tasks/main.yml new file mode 100644 index 0000000..4d071ad --- /dev/null +++ b/roles/mastodon/tasks/main.yml @@ -0,0 +1,17 @@ +--- +# Load distro specific variables +- include_vars: "{{ ansible_distribution }}.yml" + tags: always +- include_vars: "{{ ansible_fqdn }}.yml" + ignore_errors: True + +- include_tasks: system_setup/prepare_packagemanager.yml + +- include_role: name=database +- include_tasks: system_setup/prepare_database.yml +- include_role: name=webserver +- include_tasks: system_setup/nginx.yml +- include_tasks: system_setup/user.yml +- include_tasks: system_setup/ruby.yml +- include_tasks: system_setup/mastodon.yml +- include_tasks: system_setup/letsencrypt.yml \ No newline at end of file diff --git a/roles/mastodon/tasks/system_setup/letsencrypt.yml b/roles/mastodon/tasks/system_setup/letsencrypt.yml new file mode 100644 index 0000000..6a3444b --- /dev/null +++ b/roles/mastodon/tasks/system_setup/letsencrypt.yml @@ -0,0 +1,31 @@ +--- +- stat: path=/etc/letsencrypt/live/{{ mastodon_host }}/fullchain.pem + register: letsencrypt_cert + +- name: Copy letsencrypt nginx config + template: + src: ../files/nginx/letsencrypt.conf.j2 + dest: /etc/nginx/sites-available/mastodon.conf + when: not letsencrypt_cert.stat.exists + +- name: Symlink enabled site + file: + src: "/etc/nginx/sites-available/mastodon.conf" + dest: "/etc/nginx/sites-enabled/mastodon.conf" + state: link + when: not letsencrypt_cert.stat.exists + +- name: Reload nginx + command: "systemctl reload nginx" + +- name: Install letsencrypt cert + command: letsencrypt certonly -n --webroot -d {{ mastodon_host }} -w {{ mastodon_home }}/{{ mastodon_path }}/public/ --email "webmaster@{{ mastodon_host }}" --agree-tos && systemctl reload nginx + when: not letsencrypt_cert.stat.exists + +- name: Letsencrypt Job + cron: + name: "letsencrypt renew" + minute: "15" + hour: "0" + job: "letsencrypt renew && service nginx reload" + diff --git a/roles/mastodon/tasks/system_setup/mastodon.yml b/roles/mastodon/tasks/system_setup/mastodon.yml new file mode 100644 index 0000000..2f25c4c --- /dev/null +++ b/roles/mastodon/tasks/system_setup/mastodon.yml @@ -0,0 +1,100 @@ +- name: Clone mastodon + git: + repo: "https://github.com/mastodon/mastodon.git" + dest: "{{ mastodon_home }}/{{mastodon_path}}" + clone: true + +# - name: Update to latest version +# shell: "git fetch; git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)" +# args: +# chdir: "{{ mastodon_home }}/{{ mastodon_path }}" + +- name: Bundle install + shell: | + ~/.rbenv/shims/bundle config set --local deployment 'true' && \ + ~/.rbenv/shims/bundle config set --local without 'test' && \ + ~/.rbenv/shims/bundle config set --local with 'development' && \ + ~/.rbenv/shims/bundle install -j$(getconf _NPROCESSORS_ONLN) + args: + chdir: "{{ mastodon_home }}/{{ mastodon_path }}" + +- name: Yarn install + command: yarn install --pure-lockfile + args: + chdir: "{{ mastodon_home }}/{{ mastodon_path }}" + +- name: Install systemd sidekiq Service Files + template: + src: mastodon-sidekiq.service.j2 + dest: /etc/systemd/system/mastodon-sidekiq.service + become: true + become_user: root + +- name: Install systemd web Service Files + template: + src: mastodon-web.service.j2 + dest: /etc/systemd/system/mastodon-web.service + become: true + become_user: root + +- name: Install systemd streaming Service Files + template: + src: mastodon-streaming.service.j2 + dest: /etc/systemd/system/mastodon-streaming.service + become: true + become_user: root + +- name: Media cleanup cronjob + cron: + name: "media cleanup" + minute: "15" + hour: "1" + job: '/bin/bash -c ''export PATH="$HOME/.rbenv/bin:$PATH"; eval "$(rbenv init -)"; cd {{ mastodon_home }}/{{ mastodon_path }} && RAILS_ENV=production ./bin/tootctl media remove''' + +- stat: path={{ mastodon_home }}/{{ mastodon_path }}/.env.production + register: production_config + +- name: Migrate database + shell: "RAILS_ENV=production ~/.rbenv/shims/bundle exec rails db:migrate" + args: + chdir: "{{ mastodon_home }}/{{ mastodon_path }}" + when: production_config.stat.exists + +- name: Precompile assets + shell: "RAILS_ENV=production ~/.rbenv/shims/bundle exec rails assets:precompile" + args: + chdir: "{{ mastodon_home }}/{{ mastodon_path }}" + when: production_config.stat.exists + +- name: Enable mastodon-web + command: systemctl enable mastodon-web.service + become: true + become_user: root + +- name: Enable mastodon-streaming + command: systemctl enable mastodon-streaming.service + become: true + become_user: root + +- name: Enable mastodon-sidekiq + command: systemctl enable mastodon-sidekiq.service + become: true + become_user: root + +- name: Restart mastodon-web + command: systemctl restart mastodon-web.service + when: production_config.stat.exists + become: true + become_user: root + +- name: Restart mastodon-streaming + command: systemctl restart mastodon-streaming.service + when: production_config.stat.exists + become: true + become_user: root + +- name: Restart mastodon-sidekiq + command: systemctl restart mastodon-sidekiq.service + when: production_config.stat.exists + become: true + become_user: root \ No newline at end of file diff --git a/roles/mastodon/tasks/system_setup/nginx.yml b/roles/mastodon/tasks/system_setup/nginx.yml new file mode 100644 index 0000000..7eff427 --- /dev/null +++ b/roles/mastodon/tasks/system_setup/nginx.yml @@ -0,0 +1,18 @@ +--- + +- name: mastodon | Copy nginx config + template: + src: ../files/nginx/mastodon.conf.j2 + dest: /etc/nginx/sites-available/mastodon.conf + when: + - mastodon_host is defined + notify: restart_nginx + +- name: mastodon | Symlink enabled site + file: + src: "/etc/nginx/sites-available/mastodon.conf" + dest: "/etc/nginx/sites-enabled/mastodon.conf" + state: link + when: + - mastodon_host is defined + notify: restart_nginx \ No newline at end of file diff --git a/roles/mastodon/tasks/system_setup/packages.yml b/roles/mastodon/tasks/system_setup/packages.yml new file mode 100644 index 0000000..8927e74 --- /dev/null +++ b/roles/mastodon/tasks/system_setup/packages.yml @@ -0,0 +1,16 @@ +--- + +- name: mastodon | Install packages + package: + name: "{{ item.package }}" + update_cache: yes + cache_valid_time: 3600 + state: latest + install_recommends: no + with_items: "{{ packages }}" + +- name: mastodon | nodejs alternative + alternatives: + name: node + link: /usr/bin/node + path: /usr/bin/nodejs \ No newline at end of file diff --git a/roles/mastodon/tasks/system_setup/prepare_database.yml b/roles/mastodon/tasks/system_setup/prepare_database.yml new file mode 100644 index 0000000..c4c15ae --- /dev/null +++ b/roles/mastodon/tasks/system_setup/prepare_database.yml @@ -0,0 +1,47 @@ +- name: mastodon | postgres | Create database {{ mastodon_db }} + postgresql_db: + name: mastodon | postgres | "{{ mastodon_db }}" + login_host: "{{ mastodon_db_login_host }}" + login_password: "{{ mastodon_db_login_password }}" + login_user: "{{ mastodon_db_login_user }}" + port: "{{ mastodon_db_port }}" + register: create_remote_db + when: + - mastodon_db_login_user is defined + - mastodon_db_login_host is defined + - mastodon_db_login_password is defined + - mastodon_db_port is defined + +- name: mastodon | postgres | Create database user {{ mastodon_db_user }} + postgresql_user: + db: "{{ mastodon_db }}" + name: mastodon | postgres | "{{ mastodon_db_user }}" + password: "{{ mastodon_db_password }}" + login_host: "{{ mastodon_db_login_host }}" + login_password: "{{ mastodon_db_login_password }}" + login_user: "{{ mastodon_db_login_user }}" + port: "{{ mastodon_db_port }}" + role_attr_flags: CREATEDB + register: create_remote_db_user + when: + - mastodon_db_login_user is defined + - mastodon_db_login_host is defined + - mastodon_db_login_password is defined + - mastodon_db_port is defined + +- name: mastodon | postgres | Create database {{ mastodon_db }} + postgresql_db: + name: mastodon | postgres | "{{ mastodon_db }}" + login_unix_socket: "{{ mastodon_db_login_unix_socket }}" + register: create_local_db + when: create_remote_db is skipped + +- name: mastodon | postgres | Create database user {{ mastodon_db_user }} + postgresql_user: + db: "{{ mastodon_db }}" + name: mastodon | postgres | "{{ mastodon_db_user }}" + password: "{{ mastodon_db_password }}" + encrypted: yes + login_unix_socket: "{{ mastodon_db_login_unix_socket }}" + role_attr_flags: CREATEDB + when: create_remote_db_user is skipped \ No newline at end of file diff --git a/roles/mastodon/tasks/system_setup/prepare_packagemanager.yml b/roles/mastodon/tasks/system_setup/prepare_packagemanager.yml new file mode 100644 index 0000000..9259065 --- /dev/null +++ b/roles/mastodon/tasks/system_setup/prepare_packagemanager.yml @@ -0,0 +1,22 @@ +- name: mastodon | package manager | get nodejs prepare script + +- name: mastodon | package manager | add gpg keys + apt_key: + id: "{{ item.id }}" + url: "{{ item.url }}" + state: present + loop: + - { id: "72ECF46A56B4AD39C907BBB71646B01B86E50310", url: "https://dl.yarnpkg.com/debian/pubkey.gpg" } + - { id: "9FD3B784BC1C6FC31A8A0A1C1655A0AB68576280", url: "https://deb.nodesource.com/gpgkey/nodesource.gpg.key" } + +- name: mastodon | package manager | add repos + apt_repository: + repo: "{{ item.repo }}" + state: present + mode: 0644 # not required. The octal mode for newly created files in sources.list.d + update_cache: no + validate_certs: yes # not required. If C(no), SSL certificates for the target repo will not be validated. This should only be used on personally controlled sites using self-signed certificates. + filename: "{{ item.filename }}" + loop: + - { repo: "deb https://dl.yarnpkg.com/debian/ stable main", filename: "yarn"} + - { repo: "deb https://deb.nodesource.com/node_{{ node_major_version }}.x {{ ansible_lsb.codename }} main", filename: "nodejs"} \ No newline at end of file diff --git a/roles/mastodon/tasks/system_setup/ruby.yml b/roles/mastodon/tasks/system_setup/ruby.yml new file mode 100644 index 0000000..2ee869a --- /dev/null +++ b/roles/mastodon/tasks/system_setup/ruby.yml @@ -0,0 +1,57 @@ +--- +- name: mastodon | Clone rbenv + git: + repo: "https://github.com/rbenv/rbenv.git" + dest: "~/.rbenv" + clone: true + version: "{{ rbenv_version }}" + +- name: mastodon | Clone ruby-build + git: + repo: "https://github.com/rbenv/ruby-build.git" + dest: "~/.rbenv/plugins/ruby-build" + clone: true + version: "{{ ruby_build_version }}" + register: ruby_build + +- name: mastodon | Configure rbenv + command: ./configure + args: + chdir: "~/.rbenv/src" + register: rbenv_configure + +- name: mastodon | Build rbenv + command: make + args: + chdir: "~/.rbenv/src" + when: rbenv_configure is succeeded + +- name: mastodon | Update profile settings + copy: + dest: "~/.bashrc" + content: | + export PATH="~/.rbenv/bin:${PATH}" + eval "$(rbenv init -)" +- name: mastodon | Check if the Ruby version is already installed + shell: "~/.rbenv/bin/rbenv versions | grep -q {{ ruby_version }}" + register: ruby_installed + ignore_errors: yes + check_mode: no + +- name: mastodon | Install Ruby {{ ruby_version }} + shell: "~/.rbenv/bin/rbenv install {{ ruby_version }}" + args: + executable: /bin/bash + when: ruby_installed is failed + +- name: mastodon | Set the default Ruby version to {{ ruby_version }} + shell: "~/.rbenv/bin/rbenv global {{ ruby_version }}" + args: + executable: /bin/bash + register: default_ruby_version + +- name: mastodon | Install bundler + shell: 'export PATH="$HOME/.rbenv/bin:$PATH"; eval "$(rbenv init -)"; gem install bundler:{{ bundler_version }}' + args: + executable: /bin/bash + when: default_ruby_version is succeeded \ No newline at end of file diff --git a/roles/mastodon/tasks/system_setup/user.yml b/roles/mastodon/tasks/system_setup/user.yml new file mode 100644 index 0000000..28d9676 --- /dev/null +++ b/roles/mastodon/tasks/system_setup/user.yml @@ -0,0 +1,6 @@ +- name: mastodon | create mastodon user + user: + name: "{{ mastodon_user }}" + createhome: true + shell: /bin/bash + home: "{{ mastodon_home }}" \ No newline at end of file diff --git a/roles/mastodon/templates/letsencrypt.conf.j2 b/roles/mastodon/templates/letsencrypt.conf.j2 new file mode 100644 index 0000000..d496a89 --- /dev/null +++ b/roles/mastodon/templates/letsencrypt.conf.j2 @@ -0,0 +1,8 @@ +# This starts a simple nginx for the letsencrypt acme challenge +server { + listen 80; + listen [::]:80; + server_name {{ mastodon_host }}; + root {{ mastodon_home }}/{{ mastodon_path }}/public; + location /.well-known/acme-challenge/ { allow all; } +} \ No newline at end of file diff --git a/roles/mastodon/templates/mastodon-sidekiq.service.j2 b/roles/mastodon/templates/mastodon-sidekiq.service.j2 new file mode 100644 index 0000000..466297e --- /dev/null +++ b/roles/mastodon/templates/mastodon-sidekiq.service.j2 @@ -0,0 +1,16 @@ +[Unit] +Description=mastodon-sidekiq +After=network.target + +[Service] +Type=simple +User=mastodon +WorkingDirectory={{ mastodon_home }}/{{ mastodon_path }} +Environment="RAILS_ENV=production" +Environment="DB_POOL=5" +ExecStart={{ mastodon_home }}/.rbenv/shims/bundle exec sidekiq -c 5 -q default -q push -q mailers -q pull +TimeoutSec=15 +Restart=always + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/roles/mastodon/templates/mastodon-streaming.service.j2 b/roles/mastodon/templates/mastodon-streaming.service.j2 new file mode 100644 index 0000000..cf2a8ff --- /dev/null +++ b/roles/mastodon/templates/mastodon-streaming.service.j2 @@ -0,0 +1,16 @@ +[Unit] +Description=mastodon-streaming +After=network.target + +[Service] +Type=simple +User=mastodon +WorkingDirectory={{ mastodon_home }}/{{ mastodon_path }} +Environment="NODE_ENV=production" +Environment="PORT=4000" +ExecStart=/usr/bin/npm run start +TimeoutSec=15 +Restart=always + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/roles/mastodon/templates/mastodon-web.service.j2 b/roles/mastodon/templates/mastodon-web.service.j2 new file mode 100644 index 0000000..747684f --- /dev/null +++ b/roles/mastodon/templates/mastodon-web.service.j2 @@ -0,0 +1,17 @@ +[Unit] +Description=mastodon-web +After=network.target + +[Service] +Type=simple +User=mastodon +WorkingDirectory={{ mastodon_home }}/{{ mastodon_path }} +Environment="RAILS_ENV=production" +Environment="PORT=3000" +ExecStart={{ mastodon_home }}/.rbenv/shims/bundle exec puma -C config/puma.rb +ExecReload=/bin/kill -SIGUSR1 $MAINPID +TimeoutSec=15 +Restart=always + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/roles/mastodon/templates/mastodon.conf.j2 b/roles/mastodon/templates/mastodon.conf.j2 new file mode 100644 index 0000000..5bb2931 --- /dev/null +++ b/roles/mastodon/templates/mastodon.conf.j2 @@ -0,0 +1,100 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 80; + listen [::]:80; + server_name {{ mastodon_host }}; + + # Useful for Let's Encrypt + location /.well-known/acme-challenge/ { allow all; } + location / { return 301 https://$host$request_uri; } +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name {{ mastodon_host }}; + + ssl_protocols TLSv1.2; + ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + +{% if disable_letsencrypt != "true" %} + ssl_certificate /etc/letsencrypt/live/{{ mastodon_host }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ mastodon_host }}/privkey.pem; +{% endif %} + + keepalive_timeout 70; + sendfile on; + client_max_body_size 8m; + + root {{ mastodon_home }}/{{ mastodon_path }}/public; + + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + +{% if disable_hsts == "true" %} + add_header Strict-Transport-Security "max-age=31536000"; +{% endif %} + + location / { + try_files $uri @proxy; + } + + location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri @proxy; + } + + location /sw.js { + add_header Cache-Control "public, max-age=0"; + try_files $uri @proxy; + } + + location @proxy { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Proxy ""; + proxy_pass_header Server; + + proxy_pass http://127.0.0.1:3000; + proxy_buffering off; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + tcp_nodelay on; + } + + location /api/v1/streaming { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Proxy ""; + + proxy_pass http://127.0.0.1:4000; + proxy_buffering off; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + tcp_nodelay on; + } + + error_page 500 501 502 503 504 /500.html; +} \ No newline at end of file diff --git a/roles/mastodon/vars/debian.yml b/roles/mastodon/vars/debian.yml new file mode 100644 index 0000000..da091ae --- /dev/null +++ b/roles/mastodon/vars/debian.yml @@ -0,0 +1,33 @@ +mastodon_db_login_unix_socket: '/var/run/postgresql' + +packages: + - package: "autoconf" + - package: "bison" + - package: "build-essential" + - package: "curl" + - package: "cron" + - package: "ffmpeg" + - package: "file" + - package: "g++" + - package: "gcc" + - package: "git" + - package: "imagemagick" + - package: "libffi-dev" + - package: "libgdbm-dev" + - package: "libicu-dev" + - package: "libidn11-dev" + - package: "libncurses5-dev" + - package: "libpq-dev" + - package: "libprotobuf-dev" + - package: "libreadline-dev" + - package: "libssl-dev" + - package: "libxml2-dev" + - package: "libxslt1-dev" + - package: "libyaml-dev" + - package: "nodejs" + - package: "pkg-config" + - package: "protobuf-compiler" + - package: "sudo" + - package: "systemd" + - package: "yarn" + - package: "zlib1g-dev" \ No newline at end of file diff --git a/roles/mastodon/vars/main.yml b/roles/mastodon/vars/main.yml new file mode 100644 index 0000000..d6fdbc5 --- /dev/null +++ b/roles/mastodon/vars/main.yml @@ -0,0 +1,20 @@ +webserver: True +nginx: True +database: True +postgres: True +redis: True + +ruby_version: "2.7.4" +rbenv_version: "v1.1.2" +ruby_build_version: "v20210707" +bundler_version: "2.1.4" +node_major_version: "12" +os_family: "{{ ansible_os_family|lower }}" +mastodon_user: "mastodon" +mastodon_home: "/home/{{ mastodon_user }}" +mastodon_db_user: "{{ mastodon_user }}" +mastodon_path: "live" +mastodon_db: "{{ mastodon_user }}_development" +mastodon_db_port: 5432 +disable_hsts: "false" +disable_letsencrypt: "false" \ No newline at end of file