diff --git a/environments/production.json b/environments/production.json index ac5f7e6..635c0f3 100644 --- a/environments/production.json +++ b/environments/production.json @@ -16,6 +16,11 @@ "use_ssl": "false" } } + }, + "kosmos-mastodon": { + "alternate_domains": [ + "mastodon.w7nooprauv6yrnhzh2ajpcnj3doinked2aaztlwfyt6u6pva2qdxqhid.onion" + ] } } } \ No newline at end of file diff --git a/nodes/fornax.kosmos.org.json b/nodes/fornax.kosmos.org.json index 57701b7..b3683d7 100644 --- a/nodes/fornax.kosmos.org.json +++ b/nodes/fornax.kosmos.org.json @@ -1,5 +1,6 @@ { "name": "fornax.kosmos.org", + "chef_environment": "production", "normal": { "knife_zero": { "host": "10.1.1.147" @@ -25,18 +26,25 @@ "kosmos-base::default", "kosmos_kvm::host", "kosmos_kvm::backup", + "tor-full", + "tor-full::default", "kosmos_assets::nginx_site", "kosmos_discourse::nginx", "kosmos_drone::nginx", "kosmos_gitea::nginx", "kosmos_website", "kosmos_website::default", - "kosmos-ejabberd::nginx", "kosmos-akkounts::nginx_api", + "kosmos-ejabberd::nginx", "kosmos-hubot::nginx_botka_irc-libera-chat", "kosmos-hubot::nginx_hal8000_xmpp", "kosmos-ipfs::nginx_public_gateway", + "kosmos-mastodon::nginx", "remotestorage_discourse::nginx", + "kosmos_garage", + "kosmos_garage::default", + "kosmos_garage::firewall_rpc", + "kosmos_garage::nginx_web", "kosmos_zerotier::controller", "kosmos_zerotier::firewall", "kosmos_zerotier::zncui", diff --git a/nodes/mastodon-3.json b/nodes/mastodon-3.json index fe1625b..18e8250 100644 --- a/nodes/mastodon-3.json +++ b/nodes/mastodon-3.json @@ -1,5 +1,6 @@ { "name": "mastodon-3", + "chef_environment": "production", "normal": { "knife_zero": { "host": "10.1.1.30" @@ -23,7 +24,6 @@ "kosmos_postgresql::hostsfile", "kosmos-mastodon", "kosmos-mastodon::default", - "kosmos-mastodon::nginx", "kosmos-mastodon::backup", "apt::default", "timezone_iii::default", @@ -55,24 +55,14 @@ "redisio::disable_os_default", "redisio::configure", "redisio::enable", + "firewall::default", + "chef-sugar::default", "nodejs::npm", "nodejs::install", - "kosmos-nginx::default", - "nginx::default", - "nginx::package", - "nginx::ohai_plugin", - "nginx::repo", - "nginx::commons", - "nginx::commons_dir", - "nginx::commons_script", - "nginx::commons_conf", - "kosmos-nginx::firewall", - "tor-full::default", "backup::default", "logrotate::default", "git::default", - "git::package", - "kosmos-base::letsencrypt" + "git::package" ], "platform": "ubuntu", "platform_version": "20.04", diff --git a/roles/mastodon.rb b/roles/mastodon.rb index a69e384..ab0be5d 100644 --- a/roles/mastodon.rb +++ b/roles/mastodon.rb @@ -3,6 +3,5 @@ name "mastodon" run_list %w( role[postgresql_client] kosmos-mastodon - kosmos-mastodon::nginx kosmos-mastodon::backup ) diff --git a/roles/nginx_proxy.rb b/roles/nginx_proxy.rb index 13dbba2..0edd22b 100644 --- a/roles/nginx_proxy.rb +++ b/roles/nginx_proxy.rb @@ -1,17 +1,35 @@ name "nginx_proxy" +override_attributes( + 'nginx' => { + 'server_names_hash_bucket_size' => 128 + }, + 'tor' => { + 'HiddenServices' => { + 'web' => { + 'HiddenServicePorts' => ['80 127.0.0.1:80', '443 127.0.0.1:443'] + } + } + } +) + default_run_list = %w( + tor-full kosmos_assets::nginx_site kosmos_discourse::nginx kosmos_drone::nginx kosmos_gitea::nginx kosmos_website::default - kosmos-ejabberd::nginx kosmos-akkounts::nginx_api + kosmos-ejabberd::nginx kosmos-hubot::nginx_botka_irc-libera-chat kosmos-hubot::nginx_hal8000_xmpp kosmos-ipfs::nginx_public_gateway + kosmos-mastodon::nginx remotestorage_discourse::nginx + kosmos_garage::default + kosmos_garage::firewall_rpc + kosmos_garage::nginx_web ) env_run_lists( diff --git a/site-cookbooks/kosmos-ipfs/templates/default/nginx_conf_ipfs.kosmos.org.erb b/site-cookbooks/kosmos-ipfs/templates/default/nginx_conf_ipfs.kosmos.org.erb index 4343925..92fb220 100644 --- a/site-cookbooks/kosmos-ipfs/templates/default/nginx_conf_ipfs.kosmos.org.erb +++ b/site-cookbooks/kosmos-ipfs/templates/default/nginx_conf_ipfs.kosmos.org.erb @@ -9,8 +9,6 @@ upstream _ipfs_api { <% end %> } -limit_req_zone $binary_remote_addr zone=ipfsgateway:10m rate=10r/s; - server { listen 443 ssl http2; listen [::]:443 ssl http2; @@ -21,7 +19,6 @@ server { error_log /var/log/nginx/<%= @server_name %>.error.log; location /ipfs { - limit_req zone=ipfsgateway burst=20 nodelay; proxy_pass http://_ipfs_gateway/ipfs; } diff --git a/site-cookbooks/kosmos-mastodon/attributes/default.rb b/site-cookbooks/kosmos-mastodon/attributes/default.rb index e5fb368..c3a5406 100644 --- a/site-cookbooks/kosmos-mastodon/attributes/default.rb +++ b/site-cookbooks/kosmos-mastodon/attributes/default.rb @@ -1,16 +1,15 @@ -node.default["kosmos-mastodon"]["repo"] = "https://gitea.kosmos.org/kosmos/mastodon.git" -node.default["kosmos-mastodon"]["revision"] = "kosmos-production" -node.default["kosmos-mastodon"]["directory"] = "/opt/mastodon" -node.default["kosmos-mastodon"]["puma_port"] = 3000 -node.default["kosmos-mastodon"]["streaming_port"] = 4000 -node.default["kosmos-mastodon"]["server_name"] = "kosmos.social" -node.default["kosmos-mastodon"]["redis_url"] = "redis://localhost:6379/0" -node.default["kosmos-mastodon"]["sidekiq_threads"] = 25 +node.default["kosmos-mastodon"]["repo"] = "https://gitea.kosmos.org/kosmos/mastodon.git" +node.default["kosmos-mastodon"]["revision"] = "kosmos-production" +node.default["kosmos-mastodon"]["directory"] = "/opt/mastodon" +node.default["kosmos-mastodon"]["bind_ip"] = "127.0.0.1" +node.default["kosmos-mastodon"]["app_port"] = 3000 +node.default["kosmos-mastodon"]["streaming_port"] = 4000 +node.default["kosmos-mastodon"]["server_name"] = "kosmos.social" +node.default["kosmos-mastodon"]["alternate_domains"] = [] +node.default["kosmos-mastodon"]["redis_url"] = "redis://localhost:6379/0" +node.default["kosmos-mastodon"]["sidekiq_threads"] = 25 +node.default["kosmos-mastodon"]["onion_address"] = nil # Allocate this amount of RAM to the Java heap for Elasticsearch node.default["kosmos-mastodon"]["elasticsearch"]["allocated_memory"] = "1536m" node.override["redisio"]["version"] = "6.2.6" - -node.override["tor"]["HiddenServices"]["mastodon"] = { - "HiddenServicePorts" => ["80 127.0.0.1:80", "443 127.0.0.1:443"] -} diff --git a/site-cookbooks/kosmos-mastodon/metadata.rb b/site-cookbooks/kosmos-mastodon/metadata.rb index dc23b66..fa0b6f4 100644 --- a/site-cookbooks/kosmos-mastodon/metadata.rb +++ b/site-cookbooks/kosmos-mastodon/metadata.rb @@ -2,19 +2,20 @@ name 'kosmos-mastodon' maintainer 'Kosmos' maintainer_email 'mail@kosmos.org' license 'MIT' -description 'Installs/Configures kosmos-mastodon' +description 'Installs/Configures Mastodon' long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) version '0.2.1' -depends "kosmos-nginx" -depends "kosmos-nodejs" +depends 'application' +depends 'application_git' +depends 'backup' +depends 'elasticsearch' +depends 'java' +depends 'firewall' depends 'redisio' -depends "poise-ruby-build" -depends "application" -depends "application_git" -depends "postgresql" -depends "kosmos_postgresql" -depends "backup" -depends "elasticsearch" -depends "tor-full" -depends "java" +depends 'tor-full' +depends 'poise-ruby-build' +depends 'postgresql' +depends 'kosmos-nginx' +depends 'kosmos-nodejs' +depends 'kosmos_postgresql' diff --git a/site-cookbooks/kosmos-mastodon/recipes/default.rb b/site-cookbooks/kosmos-mastodon/recipes/default.rb index e61f1ee..2b47544 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/default.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/default.rb @@ -7,6 +7,7 @@ include_recipe "kosmos-nodejs" include_recipe "java" include_recipe 'redisio::default' include_recipe 'redisio::enable' +include_recipe 'firewall' elasticsearch_user 'elasticsearch' @@ -41,9 +42,14 @@ elasticsearch_service 'elasticsearch' postgresql_data_bag_item = data_bag_item('credentials', 'postgresql') mastodon_path = node["kosmos-mastodon"]["directory"] - mastodon_user = "mastodon" +bind_ip = if node.chef_environment == "production" + node["knife_zero"]["host"] + else + node["kosmos-mastodon"]["bind_ip"] + end + group mastodon_user do gid 62786 end @@ -64,7 +70,7 @@ npm_package "yarn" do version "1.22.4" end -ruby_version = "3.0.3" +ruby_version = "3.0.4" execute "systemctl daemon-reload" do command "systemctl daemon-reload" @@ -77,7 +83,8 @@ template "/lib/systemd/system/mastodon-web.service" do source "mastodon-web.systemd.service.erb" variables user: mastodon_user, app_dir: mastodon_path, - port: node["kosmos-mastodon"]["puma_port"], + bind: bind_ip, + port: node["kosmos-mastodon"]["app_port"], bundle_path: "/opt/ruby_build/builds/#{ruby_version}/bin/bundle" notifies :run, "execute[systemctl daemon-reload]", :immediately notifies :restart, "service[mastodon-web]", :delayed @@ -113,6 +120,7 @@ template "/lib/systemd/system/mastodon-streaming.service" do source "mastodon-streaming.systemd.service.erb" variables user: mastodon_user, app_dir: mastodon_path, + bind: bind_ip, port: node["kosmos-mastodon"]["streaming_port"] notifies :run, "execute[systemctl daemon-reload]", :immediately notifies :restart, "service[mastodon-streaming]", :delayed @@ -151,6 +159,7 @@ application mastodon_path do group mastodon_user variables redis_url: node["kosmos-mastodon"]["redis_url"], domain: node["kosmos-mastodon"]["server_name"], + alternate_domains: node["kosmos-mastodon"]["alternate_domains"], paperclip_secret: mastodon_credentials['paperclip_secret'], secret_key_base: mastodon_credentials['secret_key_base'], otp_secret: mastodon_credentials['otp_secret'], @@ -183,7 +192,6 @@ application mastodon_path do end execute 'rake db:migrate' do - # environment "RAILS_ENV" => "production", "HOME" => mastodon_path#, "SKIP_POST_DEPLOYMENT_MIGRATIONS" => "true" environment "RAILS_ENV" => "production", "HOME" => mastodon_path, "SKIP_POST_DEPLOYMENT_MIGRATIONS" => "true" user mastodon_user group mastodon_user @@ -215,3 +223,17 @@ application mastodon_path do action [:enable, :start] end end + +firewall_rule 'mastodon_app' do + port node['kosmos-mastodon']['app_port'] + source "10.1.1.0/24" + protocol :tcp + command :allow +end + +firewall_rule 'mastodon_streaming' do + port node['kosmos-mastodon']['streaming_port'] + source "10.1.1.0/24" + protocol :tcp + command :allow +end diff --git a/site-cookbooks/kosmos-mastodon/recipes/nginx.rb b/site-cookbooks/kosmos-mastodon/recipes/nginx.rb index 84caba4..34575d0 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/nginx.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/nginx.rb @@ -3,12 +3,23 @@ # Recipe:: nginx # -mastodon_path = node["kosmos-mastodon"]["directory"] -server_name = node["kosmos-mastodon"]["server_name"] - -node.override['nginx']['server_names_hash_bucket_size'] = 128 include_recipe "kosmos-nginx" +app_dir = node["kosmos-mastodon"]["directory"] +server_name = node["kosmos-mastodon"]["server_name"] +is_proxy = node.roles.include?('nginx_proxy') rescue nil +upstream_hosts = [] + +if is_proxy + web_root_dir = "/var/www/#{server_name}/public" + search(:node, "role:mastodon").each do |node| + upstream_hosts << node["knife_zero"]["host"] + end +else + web_root_dir = "#{app_dir}/public" + upstream_hosts << "localhost" +end + directory "#{node['nginx']['dir']}/snippets" do action :create owner 'www-data' @@ -19,40 +30,30 @@ template "#{node['nginx']['dir']}/snippets/mastodon.conf" do source 'nginx_conf_shared.erb' owner 'www-data' mode 0640 - variables streaming_port: node["kosmos-mastodon"]["streaming_port"], - puma_port: node["kosmos-mastodon"]["puma_port"], - mastodon_path: mastodon_path + variables web_root_dir: web_root_dir, + server_name: server_name notifies :reload, 'service[nginx]', :delayed end -onion_address = File.read("/var/lib/tor/mastodon/hostname").strip rescue nil +nginx_certbot_site server_name + +onion_address = File.read("/var/lib/tor/web/hostname").strip rescue nil rescue nil template "#{node['nginx']['dir']}/sites-available/#{server_name}" do source 'nginx_conf_mastodon.erb' owner 'www-data' mode 0640 - variables server_name: server_name, - ssl_cert: "/etc/letsencrypt/live/#{server_name}/fullchain.pem", - ssl_key: "/etc/letsencrypt/live/#{server_name}/privkey.pem", - shared_config_path: "#{node['nginx']['dir']}/snippets/mastodon.conf", - onion_address: onion_address + variables server_name: server_name, + ssl_cert: "/etc/letsencrypt/live/#{server_name}/fullchain.pem", + ssl_key: "/etc/letsencrypt/live/#{server_name}/privkey.pem", + shared_config_path: "#{node['nginx']['dir']}/snippets/mastodon.conf", + app_port: node["kosmos-mastodon"]["app_port"], + streaming_port: node["kosmos-mastodon"]["streaming_port"], + onion_address: onion_address, + upstream_hosts: upstream_hosts notifies :reload, 'service[nginx]', :delayed end -# Legacy vhost -nginx_site "mastodon" do - action :disable -end - nginx_site server_name do action :enable end - -nginx_certbot_site server_name - -# -# Tor hidden service -# -# The attributes for the hidden service are set in attributes/default.rb, due -# to the way the tor-full cookbook builds the path to the hidden service dir -include_recipe "tor-full" diff --git a/site-cookbooks/kosmos-mastodon/templates/default/env.production.erb b/site-cookbooks/kosmos-mastodon/templates/default/env.production.erb index 7cb7adc..5fb076b 100644 --- a/site-cookbooks/kosmos-mastodon/templates/default/env.production.erb +++ b/site-cookbooks/kosmos-mastodon/templates/default/env.production.erb @@ -32,6 +32,9 @@ SMTP_FROM_ADDRESS=<%= @smtp_from_address %> # Optional asset host for multi-server setups # CDN_HOST=assets.example.com +# Serve static files (to nginx proxy) +RAILS_SERVE_STATIC_FILES=true + # S3 (optional) S3_ENABLED=true S3_BUCKET=<%= @s3_bucket %> @@ -55,3 +58,4 @@ ES_HOST=localhost ES_PORT=9200 ALLOW_ACCESS_TO_HIDDEN_SERVICE=true +ALTERNATE_DOMAINS='<%= @alternate_domains.join(" ") %>' diff --git a/site-cookbooks/kosmos-mastodon/templates/default/mastodon-sidekiq.systemd.service.erb b/site-cookbooks/kosmos-mastodon/templates/default/mastodon-sidekiq.systemd.service.erb index eea08fd..73d758e 100644 --- a/site-cookbooks/kosmos-mastodon/templates/default/mastodon-sidekiq.systemd.service.erb +++ b/site-cookbooks/kosmos-mastodon/templates/default/mastodon-sidekiq.systemd.service.erb @@ -10,7 +10,7 @@ WorkingDirectory=<%= @app_dir %> Environment="RAILS_ENV=production" Environment="DB_POOL=50" Environment="LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" -ExecStart=<%= @bundle_path %> exec sidekiq -c <%= @sidekiq_threads %> -q default -q mailers -q pull -q push +ExecStart=<%= @bundle_path %> exec sidekiq -c <%= @sidekiq_threads %> -q default -q mailers -q pull -q push -q ingress TimeoutSec=15 Restart=always diff --git a/site-cookbooks/kosmos-mastodon/templates/default/mastodon-streaming.systemd.service.erb b/site-cookbooks/kosmos-mastodon/templates/default/mastodon-streaming.systemd.service.erb index dfdc7ff..9c62a28 100644 --- a/site-cookbooks/kosmos-mastodon/templates/default/mastodon-streaming.systemd.service.erb +++ b/site-cookbooks/kosmos-mastodon/templates/default/mastodon-streaming.systemd.service.erb @@ -6,6 +6,7 @@ Type=simple User=<%= @user %> WorkingDirectory=<%= @app_dir %> Environment="NODE_ENV=production" +Environment="BIND=<%= @bind %>" Environment="PORT=<%= @port %>" ExecStart=/usr/bin/npm run start TimeoutSec=15 diff --git a/site-cookbooks/kosmos-mastodon/templates/default/mastodon-web.systemd.service.erb b/site-cookbooks/kosmos-mastodon/templates/default/mastodon-web.systemd.service.erb index 59625d0..93a694e 100644 --- a/site-cookbooks/kosmos-mastodon/templates/default/mastodon-web.systemd.service.erb +++ b/site-cookbooks/kosmos-mastodon/templates/default/mastodon-web.systemd.service.erb @@ -9,7 +9,8 @@ User=<%= @user %> PIDFile=<%= @app_dir %>/tmp/puma.pid WorkingDirectory=<%= @app_dir %> Environment="RAILS_ENV=production" -Environment="PORT=3000" +Environment="BIND=<%= @bind %>" +Environment="PORT=<%= @port %>" Environment="LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" ExecStart=<%= @bundle_path %> exec puma -C config/puma.rb --pidfile <%= @app_dir %>/tmp/puma.pid ExecStop=<%= @bundle_path %> exec puma -C config/puma.rb --pidfile <%= @app_dir %>/tmp/puma.pid stop diff --git a/site-cookbooks/kosmos-mastodon/templates/default/nginx_conf_mastodon.erb b/site-cookbooks/kosmos-mastodon/templates/default/nginx_conf_mastodon.erb index 7c7c26d..619f03d 100644 --- a/site-cookbooks/kosmos-mastodon/templates/default/nginx_conf_mastodon.erb +++ b/site-cookbooks/kosmos-mastodon/templates/default/nginx_conf_mastodon.erb @@ -1,17 +1,24 @@ -<% if @onion_address %> -server { - listen 80; - server_name mastodon.<%= @onion_address %>; - include <%= @shared_config_path %>; -} +upstream mastodon_app { +<% @upstream_hosts.each do |host| %> + server <%= host %>:<%= @app_port %>; <% end %> +} + +upstream mastodon_streaming { +<% @upstream_hosts.each do |host| %> + server <%= host %>:<%= @streaming_port %>; +<% end %> +} map $http_upgrade $connection_upgrade { default upgrade; '' close; } -<% if File.exist?(@ssl_cert) && File.exist?(@ssl_key) %> +proxy_cache_path /var/cache/nginx/mastodon levels=1:2 + keys_zone=mastodon_cache:10m + max_size=1g inactive=120m use_temp_path=off; + server { listen 443 ssl http2; listen [::]:443 ssl http2; @@ -22,11 +29,17 @@ server { ssl_certificate_key <%= @ssl_key %>; add_header Strict-Transport-Security "max-age=31536000"; +<% if @onion_address %> add_header Onion-Location https://mastodon.<%= @onion_address %>$request_uri; -} <% end %> +} <% if @onion_address %> +server { + listen 80; + server_name mastodon.<%= @onion_address %>; + include <%= @shared_config_path %>; +} server { listen 443 ssl http2; server_name mastodon.<%= @onion_address %>; diff --git a/site-cookbooks/kosmos-mastodon/templates/default/nginx_conf_shared.erb b/site-cookbooks/kosmos-mastodon/templates/default/nginx_conf_shared.erb index 1a4e6c6..3083dd4 100644 --- a/site-cookbooks/kosmos-mastodon/templates/default/nginx_conf_shared.erb +++ b/site-cookbooks/kosmos-mastodon/templates/default/nginx_conf_shared.erb @@ -1,12 +1,12 @@ -access_log "/var/log/nginx/mastodon.access.log"; -error_log "/var/log/nginx/mastodon.error.log"; +access_log "/var/log/nginx/<%= @server_name %>.access.log"; +error_log "/var/log/nginx/<%= @server_name %>.error.log"; keepalive_timeout 70; sendfile on; client_max_body_size 0; -root <%= @mastodon_path %>/public; +root <%= @web_root_dir %>; gzip on; gzip_disable "msie6"; @@ -19,20 +19,21 @@ gzip_types text/plain text/css application/json application/javascript text/xml location / { # If the maintenance file is present, show maintenance page - if (-f <%= @mastodon_path %>/public/maintenance.html) { + if (-f <%= @web_root_dir %>/maintenance.html) { return 503; } - try_files $uri @proxy; } location /sw.js { add_header Cache-Control "max-age=0, no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; + try_files $uri @proxy; } location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) { add_header Cache-Control "public, max-age=31536000, immutable"; + proxy_cache mastodon_cache; try_files $uri @proxy; } @@ -44,7 +45,7 @@ location @proxy { proxy_set_header Proxy ""; proxy_pass_header Server; - proxy_pass http://localhost:<%= @puma_port %>; + proxy_pass http://mastodon_app; proxy_buffering off; proxy_redirect off; proxy_http_version 1.1; @@ -61,7 +62,7 @@ location /api/v1/streaming { proxy_set_header X-Forwarded-Proto https; proxy_set_header Proxy ""; - proxy_pass http://localhost:<%= @streaming_port %>; + proxy_pass http://mastodon_streaming; proxy_buffering off; proxy_redirect off; proxy_http_version 1.1; @@ -75,5 +76,5 @@ error_page 500 501 502 504 /500.html; error_page 503 /maintenance.html; location = /maintenance.html { - root <%= @mastodon_path %>/public; + root <%= @web_root_dir %>; }