diff --git a/clients/mastodon-4.json b/clients/mastodon-4.json new file mode 100644 index 0000000..5ed08ca --- /dev/null +++ b/clients/mastodon-4.json @@ -0,0 +1,4 @@ +{ + "name": "mastodon-4", + "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqBxz0HTAxiUU6VoTTMNj\nonS9GU/RDCq5rIxGd9n89xOoDVb59CgThYgjpthn6T8s1hSkS2Jgi+52S9vlkmXC\nrdr+YhTvqcRuB3w+OMxkaWAwcF6q/c06CzpelyKZ+K9Y1mE7jDVqy9filmZ+p893\nQ5ta4iBg5eE6zsjtVMeTALmsmlwR70vPzZj+VhDeg/TprhFzr44+OB9QNZdFRXSH\n3o0DNhWSvoxUGrt6BOvaNcofYr5XkgP9TLuUZ5p2IZmW58PLSSbTXzPEhdjqACJm\nfCl0ASTHlzbvyTA7bglWuS4HN+VldCts3Y9gVNQ3h6cxfL2aDbWG0Yx2qhAZCOMp\n2wIDAQAB\n-----END PUBLIC KEY-----\n" +} \ No newline at end of file diff --git a/data_bags/credentials/mastodon.json b/data_bags/credentials/mastodon.json index b3444b3..bfdfa13 100644 --- a/data_bags/credentials/mastodon.json +++ b/data_bags/credentials/mastodon.json @@ -1,114 +1,121 @@ { "id": "mastodon", "active_record_encryption_deterministic_key": { - "encrypted_data": "2ik8hqK7wrtxyC73DLI8FNezZiWp2rdjwaWZkTUFRj+iwvpSrGVEwMx6uxDI\nWa7zF3p/\n", - "iv": "XMp6wqwzStXZx+F3\n", - "auth_tag": "vloJOLqEcghfQXOYohVVlg==\n", + "encrypted_data": "M7PHYe8qx7TUXpbNMS8slE6p+Naq3WhGklQty8oUJ86cA0hVryqbSySfgBm7\nuEKhTBjO\n", + "iv": "tApFi3rs15Y1sCVy\n", + "auth_tag": "YZ75dXuxepIm8CK8vOd51A==\n", "version": 3, "cipher": "aes-256-gcm" }, "active_record_encryption_key_derivation_salt": { - "encrypted_data": "Nq/rHayMYmT/82k3tJUKU8YTvDKUKLoK204aT0CMGZertZaAD3dtA9AkprrA\nPK0D9CdL\n", - "iv": "tn9C+igusYMH6GyM\n", - "auth_tag": "+ReZRNrfpl6ZDwYQpwm6dw==\n", + "encrypted_data": "raLTVbWRr8KRtSL9u2hoZhlcBNzR4qzGssSngIjpVN91ueJF3KRYTf1lyd6O\nt46Il9Ye\n", + "iv": "Jhl+LvZlCmJoxMVP\n", + "auth_tag": "3RgdNDtaH4eiiQfNHeljTA==\n", "version": 3, "cipher": "aes-256-gcm" }, "active_record_encryption_primary_key": { - "encrypted_data": "UEDMuKHgZDBhpB9BwbPmtdmIDWHyS9/bSzaEbtTRvLcV8dGOE5q9lDVIIsQp\n2HE0c92p\n", - "iv": "tnB0pQ3OGDne3mN/\n", - "auth_tag": "kt234ms+bmcxJj/+FH/72Q==\n", + "encrypted_data": "nP/pdfTk1VGE+sfAbMq3FN5tyTI4Srpj/szcPDYOU/zfQFRQ3omjSfvOtHFS\nN9XQYPoU\n", + "iv": "R4JdQNPjOfqBGUzC\n", + "auth_tag": "BW+GO+mX2vpNXLOk3wlcKw==\n", "version": 3, "cipher": "aes-256-gcm" }, "paperclip_secret": { - "encrypted_data": "AlsnNTRF6GEyHjMHnC4VdzF4swMlppz/Gcp1xr0OuMEgQiOcW1oSZjDRZCRV\nmuGqZXZx64wqZyzTsJZ6ayCLsmWlPq6L21odHWyO+P/C5ubenSXnuCjpUn3/\nHs8WLX3kwVmqCRnVgDl2vEZ5H4XedSLr7R7YM7gQkM0UX4muMDWWnOTR8/x/\ni1ecwBY5RjdewwyR\n", - "iv": "RWiLePhFyPekYSl9\n", - "auth_tag": "sUq4ZX9CFKPbwDyuKQfNLQ==\n", + "encrypted_data": "plu8NMS/EuYAOxepD1lHbfVnzDcwmqFy1LhUKKUhqiQUlanzlZ2kYga8dy0D\n8s1XcVpw8Ei8Pz3LOjuTNfi/gE6yvinbJpxXFRgy2r6iUmoTDaSyk3wyybSN\nQkPPr5p79sJiybtJbgJZSLSylxswp0zsXYNiomoMXaxkGVoLRAEnZ18yKHrR\nzr6EbJp9IMDVOhQe\n", + "iv": "dyThHZkYejBTYV7s\n", + "auth_tag": "mjAiHvv40dcS5uDMz+CfGQ==\n", "version": 3, "cipher": "aes-256-gcm" }, "secret_key_base": { - "encrypted_data": "K5CmIXFa9mS4/dODBQAN9Bw0SFpbLiZAB8ewiYpkB8NDXP6X/BX8aDjW2Y4F\ncMvpFyiFldRBhrh1MSKTVYQEoJ3JhlNL9HCdPsAYbBEW70AuEBpHvOtD5OxH\nqgbH4Reuk6JX5AI8SwDD3zGrdT12mTFVNgSujzuZMvpi1Sro2HtRGAkjmnaa\nMGKrBV21O1CREJJg\n", - "iv": "/yMMmz1YtKIs5HSd\n", - "auth_tag": "WXgIVWjIdbMFlJhTD5J0JQ==\n", + "encrypted_data": "+3rZASvdx2B8if9UxJCTJC8OoaOLink/6muPcRv7DNedqF2fmAajI3sJuKYB\nslLUTweW3T+IVHnzT8RiNiY8mZ81ivkyQwqtl0qnfwrWTDB3auPdlZTIxSfn\nDz4a/Z4it8ewrPXeFfsZgxJebG313JB4EQN/LBHgJMBKkVeuDS4tl1zwSt4C\njEv01JO/7HXLVdGu\n", + "iv": "+CWPVenB0YLeUVF+\n", + "auth_tag": "0ZDAgVX7k/1JNDvzK7/Hxw==\n", "version": 3, "cipher": "aes-256-gcm" }, "otp_secret": { - "encrypted_data": "OPLnYRySSIDOcVHy2A5V+pCrz9zVIPjdpAGmCdgQkXtJfsS9NzNtxOPwrXo6\nuQlV9iPjr1Y9ljGKYytbF0fPgAa5q6Z1oHMY9vOGs/LGKj8wHDmIvxQ+Gil1\nC+dZEePmqGaySlNSB/gNzcFIvjBH3mDxHJJe9hDxSv5miNS9l9f3UvQeLP2M\nU7/aHKagL9ZHOp/d\n", - "iv": "wqJBLdZhJ7M/KRG9\n", - "auth_tag": "dv5YyZszZCrRnTleaiGd4A==\n", + "encrypted_data": "8xVxIgJLV4fM2DvNBeVwUTEuyf9TsAgiWrKgoj/dKmFriiZTMrY40qkoPJbP\nEI3NCK8Pvt1MF5izT/6jzxEjXEZRo3kWk1x3QqIbLmo/z6k4GC0JhAO8xDKL\nopGQBOj0Bjte3xsz/vBFCgmqpC1WQe/FpCyFVT1r0uVwAUkMiM/7McnRVmKJ\nPS59QJuuV1DtI6b+\n", + "iv": "j2tC9oc6U6bjV5BJ\n", + "auth_tag": "/Hi9sxV3aN6BR5Ixdf78DQ==\n", "version": 3, "cipher": "aes-256-gcm" }, "aws_access_key_id": { - "encrypted_data": "A1/gfcyrwT6i9W6aGTJ8pH4Dm4o8ACDxvooDroA/2N0szOiNyiYX\n", - "iv": "JNvf21KhdM3yoLGt\n", - "auth_tag": "2xaZql1ymPYuXuvXzT3ymA==\n", + "encrypted_data": "71TLwj7sYmHAm/cEX6lKE8MWDlceW5S0hrPLNVzVZ7IimjTNYf4g\n", + "iv": "DoSmqv6owgIL8+be\n", + "auth_tag": "ZcO902nm+MbP4+mwLACDvQ==\n", "version": 3, "cipher": "aes-256-gcm" }, "aws_secret_access_key": { - "encrypted_data": "T1tc01nACxhDgygKaiAq3LChGYSgmW8LAwr1aSxXmJ5D2NtypJDikiHrJbFZ\nfWFgm1qe4L8iD/k5+ro=\n", - "iv": "FDTPQQDLUMKW7TXx\n", - "auth_tag": "msY6PFFYhlwQ0X7gekSDiw==\n", + "encrypted_data": "6VOYHuZ27cqx5rfrWeKfUyX6lpSI9/o7MXuj/ah7ZTKSwx6GUbzbe3hzZJcM\nzlDjF8WkIkOwUt/yK5I=\n", + "iv": "F7wyRALeMvPec7i6\n", + "auth_tag": "I27zZeNdqetp9+Pvor7FVw==\n", "version": 3, "cipher": "aes-256-gcm" }, "ldap_bind_dn": { - "encrypted_data": "C/YNROVyOxmR4O2Cy52TX41EKli2bCOMzwYD+6Hz/SiKkgidnKUHlvHlbTDq\nkWwlRDM2o8esOCKaEAGPNWcNc9IHlaSsfwhr4YWnwe0=\n", - "iv": "QCQF0+vH+//+nDxr\n", - "auth_tag": "a0PbyO/7wjufqH2acDCqmQ==\n", + "encrypted_data": "d5AxFLbM5mkQ42Ev3jLF6divhVGRS29pf4V1HLT/EZoeTaY0Ag6+aRujP9ri\nHDi5j+VRaKNMtfK1NzQKl6XiCj0oGdO1EsRynpxXyJ0=\n", + "iv": "T4Qz2KrB30La0dj/\n", + "auth_tag": "4I+ySAVvfFDDnfipb+JjDA==\n", "version": 3, "cipher": "aes-256-gcm" }, "ldap_password": { - "encrypted_data": "SqwKeiyzfvvZGqH5gi35BdW3W+Fo/AQQjso1Yfp2XA==\n", - "iv": "md2/etFJ1r/BKaYg\n", - "auth_tag": "OlCCOoYSD7ukdH2yWCd6KA==\n", + "encrypted_data": "2V7nesfImnY6DooFctBupXKyexLqTUUoY5M7wQE0FA==\n", + "iv": "wjRF6W7JkUUS6Qn8\n", + "auth_tag": "PhuKjWIla3yFCprFgzxilA==\n", "version": 3, "cipher": "aes-256-gcm" }, "smtp_user_name": { - "encrypted_data": "0kzppmSSUg7lEyYnI5a0nf+xO0vSVx88rbxI+niIdzFOOBKSIL6uVHJ340dw\nMQ==\n", - "iv": "lQR77ETTtIIyaG1r\n", - "auth_tag": "smF2HRg8WdmD+MWwkT3TqA==\n", + "encrypted_data": "rQR5ut5VCbQf3dLz6els3BSU3Lj1dZp0k6EaEZnM2E41HrQbMCAgMWrepTaO\nCQ==\n", + "iv": "5e/KzhAz6ALZzxOm\n", + "auth_tag": "wBJLKVHyB1JKeZ9hS8uUIQ==\n", "version": 3, "cipher": "aes-256-gcm" }, "smtp_password": { - "encrypted_data": "1i0m9qiZA/8k8fMKo+04uyndl1UhagtHweBFICIorWALkB68edjb8OhUDxv9\nTubiXYRC\n", - "iv": "IU2x4ips9HWmKoxi\n", - "auth_tag": "BZJTDfPBvt8cf6/MbKzUJQ==\n", + "encrypted_data": "AYFleIGUXYZGqSXoDhJB9CG8jNlM1libXzxByDiOPxJH3q5vpGYl+ZThGQZ3\n1HFfKhR/\n", + "iv": "Qrvt1HLaHBqHwApE\n", + "auth_tag": "auqwRDScQfGe42Olhj/y7g==\n", "version": 3, "cipher": "aes-256-gcm" }, "vapid_private_key": { - "encrypted_data": "+LmySMvzrV3z2z7BmJG9hpvkL06mGc87RG20XQhhdAJ2Z/5uMMjev2pUf7du\ntv2qvDJAimhkZajuDGL9R3eq\n", - "iv": "Mg7NhPl31O6Z4P+v\n", - "auth_tag": "qYWPInhgoWAjg0zQ+XXt5w==\n", + "encrypted_data": "PRNWILa/ipj00zXrCknF0PZlpvPPPNtGgPuJS61Q8I4+XK517cAaDQypQFUa\nznm2KfQvHWDmQPRfS/oRYIk1\n", + "iv": "EBEzvMfR8J6X8o9J\n", + "auth_tag": "qECmv9fOw2PKgbxIYaWEnw==\n", "version": 3, "cipher": "aes-256-gcm" }, "vapid_public_key": { - "encrypted_data": "NOyc+Cech9qG2HhnhajDaJMWd1OU5Rp6hws6i4xF5mLPePMJ9mJTqzklkuMK\npYSEdtcxA3KmDt1HrFxfezYUc9xO9pvlm0BPA7XAFmF/PU7/AJbFqgPU6pX/\ntSDLSdFuMB3ky+cl4DJi+O4=\n", - "iv": "rgUglYiHB/mhqGha\n", - "auth_tag": "DEX7hdNsNLi/LIrMkdUe/Q==\n", + "encrypted_data": "A+yqJ64L2rrqSJ4WyCVh6rXJG9aMSyr/+11pvb8w8al3Ei69YoeX1DeWeD6J\nXgogGJDCoucoCAlcx9tFhmWpR9050d+d2Fuz2RvvElUYUACdSpzlKYcJ1fkl\nRSjUVH69EePjg3GTxlkCDiA=\n", + "iv": "hEjJ7NWlRjlGtFbQ\n", + "auth_tag": "2WGbglPqBR00UINDVr38hA==\n", "version": 3, "cipher": "aes-256-gcm" }, "s3_key_id": { - "encrypted_data": "rPVzrYYIbcM+ssVpdL6wpCTdzLIEKXke1+eMlPLMG2gPuoh+W3eO3nFGb/s2\n", - "iv": "/qI8F9cvnfKG7ZXE\n", - "auth_tag": "z1+MPdkO/+SCaag2ULelPg==\n", + "encrypted_data": "Jln2B+YjH6rWfgVzMmDg6oBB1PEZ0stiNFKEpTTSkht68oGXPVYOm40m7+/Q\n", + "iv": "Sz7de5LhoH6FFs93\n", + "auth_tag": "aZJjE5GCPy2QAFCHhnp6oQ==\n", "version": 3, "cipher": "aes-256-gcm" }, "s3_secret_key": { - "encrypted_data": "RMnB9kZ+slbQXfpo0udYld6S1QqBxqM1YbszdLfSAdKK9I0J3Kmvh/CQ5Fbx\nyov6LClmsl1rjtH16r7cY32M4Woq+6miERdtecyDrrYkNHz0xkA=\n", - "iv": "pO7bm3aOtjuwYjG/\n", - "auth_tag": "SRvn4z1+Vd5VAGgjG64s+Q==\n", + "encrypted_data": "Bjpc0XCQyPq3ZVggp2GHPDUHiOPX3UH6tNFeJE9lkqL7NSRmQ5r/Do7oDjQB\nT2Es7NL3rFLdVoV6tRRchkUDFzgyr2eSvEHkproU44FTTFmgDN0=\n", + "iv": "HEkOIrKktAezN1AK\n", + "auth_tag": "s54GLjslaf0e30MzW3kY5g==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "repo_deploy_key": { + "encrypted_data": "hI6a++CCP2/wn5OM86neYUyzFlvD1/w3VaDPB93cPkCqp7UJlj4mOl1J3Gpd\n4Tej/dpsEFiEWP2D15bMHPm5eD8YtD0iueRsTs7TVsgAwWcPIkBV52QHKjoB\nyn6FlA8f6wjF1D4IKhdSbBl2Se0gFPHT3FMy6NkV0dyUB60umMZhaQTS0h9+\ngbi6AYYQSxs3YkfAcQa7iyAXWSw1M4EsrrN2EQ14KN5RTjv24hgNGgUnINMa\nc7dVnz31wIdLfLZy2SlqNQqgwyz8KRXnDWdwnbvwNrJ1HMwNARabDnySUTB9\n03nExQnxG+c3+ioC7KSO++QATC9m0iWjx93m7S7VcbCwdpBZsswztQ/ix8AL\n6CddyLpcK80QrFPrSb2UjFtZuLNuX/Cbzrmm73MYqi/WKfWstKnyFBfkAJUA\nK8HnFU6gmvxyrgQp5ZI/1FNegr67sQlE0dhTYUcps5ZhpJ7ppJX4Fb3yq//8\n8NXEVxGrEwxCAMQEU5HlbBLXTLcTtLhujppPkdp4nYLdiWIMGIqnvOoeJi4n\nTg18xwdMN9G0OMH818Pj/LRBVfxzStbd1ZkemQw6\n", + "iv": "8d8CQGLoNlIyZT6h\n", + "auth_tag": "J9K5wCJ0bZl5uI2PM9+gag==\n", "version": 3, "cipher": "aes-256-gcm" } diff --git a/nodes b/nodes index b3b5042..58e8fb8 160000 --- a/nodes +++ b/nodes @@ -1 +1 @@ -Subproject commit b3b5042655b1f9f6c41e4ec46da1ed574200e05b +Subproject commit 58e8fb83fd96299723bfd8ca63b393eede8fd67a diff --git a/roles/mastodon.rb b/roles/mastodon.rb index b35d2b8..42ba22a 100644 --- a/roles/mastodon.rb +++ b/roles/mastodon.rb @@ -1,8 +1,11 @@ name "mastodon" +default_attributes 'kosmos-mastodon' => { + 'deploy' => true +} + run_list %w( role[postgresql_client] kosmos-mastodon::libretranslate kosmos-mastodon - kosmos-mastodon::backup ) diff --git a/site-cookbooks/kosmos-akkounts/recipes/default.rb b/site-cookbooks/kosmos-akkounts/recipes/default.rb index c673d9e..f6c0336 100644 --- a/site-cookbooks/kosmos-akkounts/recipes/default.rb +++ b/site-cookbooks/kosmos-akkounts/recipes/default.rb @@ -298,7 +298,9 @@ execute "bundle install" do environment deploy_env user deploy_user cwd deploy_path - command "bundle install --without development,test --deployment" + command "bundle config set --local deployment true && " \ + "bundle config set --local without 'development test' && " \ + "bundle install" end execute 'rake db:migrate' do diff --git a/site-cookbooks/kosmos-mastodon/attributes/default.rb b/site-cookbooks/kosmos-mastodon/attributes/default.rb index 921f0f5..31f88e3 100644 --- a/site-cookbooks/kosmos-mastodon/attributes/default.rb +++ b/site-cookbooks/kosmos-mastodon/attributes/default.rb @@ -1,13 +1,37 @@ -node.default["kosmos-mastodon"]["repo"] = "https://gitea.kosmos.org/kosmos/mastodon.git" -node.default["kosmos-mastodon"]["revision"] = "production-4.3" +node.default["kosmos-mastodon"]["repo"] = "git@gitea.kosmos.org:kosmos/mastodon.git" +node.default["kosmos-mastodon"]["revision"] = "production-4.7" 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"]["domain"] = "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 + +# Only run the Mastodon deployment (code, build, migrations, services) when this +# is true. Set to false to only install the runtime dependencies. +node.default["kosmos-mastodon"]["deploy"] = true + +# Only check out and build the application (no migrations, no services) when +# this is true. Implied by `deploy`. Useful to pre-stage a deployment without +# touching the database. +node.default["kosmos-mastodon"]["build"] = true + +# Runtime versions +node.default["kosmos-mastodon"]["nodejs_version"] = "24.21.0" +node.default["kosmos-mastodon"]["ruby_version"] = "4.0.7" +node.default["kosmos-mastodon"]["ruby_build_version"] = "v20260924" + +# SSH deploy key access to the (private) repository. The private key is stored +# in the credentials/mastodon data bag as `repo_deploy_key`; this is the pinned +# host key of gitea.kosmos.org. +node.default["kosmos-mastodon"]["gitea_ssh_host_key"] = + "gitea.kosmos.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJycWc3U9P/6BzE0HcPiTdmaDN8zKRx+0/jGXYuKiwx7" + +# External Redis cluster (see the kosmos_redis cookbook) +node.default["kosmos-mastodon"]["redis_server_role"] = "redis_server" +node.default["kosmos-mastodon"]["redis_port"] = 6379 +node.default["kosmos-mastodon"]["redis_db"] = 2 node.default["kosmos-mastodon"]["allowed_private_addresses"] = "127.0.0.1" node.default["kosmos-mastodon"]["onion_address"] = nil @@ -25,6 +49,10 @@ node.default["kosmos-mastodon"]["sso_account_reset_password_url"] = "https://acc node.default["kosmos-mastodon"]["sso_account_resend_confirmation_url"] = "https://accounts.kosmos.org/users/confirmation/new" node.default["kosmos-mastodon"]["default_locale"] = "en" +# From Mastodon 4.4 on, DEFAULT_LOCALE no longer overrides the browser language +# of unauthenticated users unless this is set to true. +node.default["kosmos-mastodon"]["force_default_locale"] = true +# Mastodon 4.6 introduced optional email subscriptions which can cause +# additional outgoing mail/costs. Disabled by default. +node.default["kosmos-mastodon"]["disable_email_subscriptions"] = true node.default["kosmos-mastodon"]["libre_translate_endpoint"] = nil - -node.override["redisio"]["version"] = "6.2.6" diff --git a/site-cookbooks/kosmos-mastodon/metadata.rb b/site-cookbooks/kosmos-mastodon/metadata.rb index 1f31d47..74dccb4 100644 --- a/site-cookbooks/kosmos-mastodon/metadata.rb +++ b/site-cookbooks/kosmos-mastodon/metadata.rb @@ -6,11 +6,8 @@ description 'Installs/Configures Mastodon' long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) version '0.2.1' -depends 'backup' depends 'elasticsearch' -depends 'java' depends 'firewall' -depends 'redisio' depends 'postgresql' depends 'kosmos-nodejs' depends 'kosmos_openresty' diff --git a/site-cookbooks/kosmos-mastodon/recipes/backup.rb b/site-cookbooks/kosmos-mastodon/recipes/backup.rb deleted file mode 100644 index 921bf35..0000000 --- a/site-cookbooks/kosmos-mastodon/recipes/backup.rb +++ /dev/null @@ -1,17 +0,0 @@ -# -# Cookbook Name:: kosmos-mastodon -# Recipe:: backup -# - -postgresql_data_bag_item = data_bag_item('credentials', 'postgresql') - -unless node.chef_environment == "development" - node.override['backup']['s3']['keep'] = 1 - node.override["backup"]["postgresql"]["host"] = "pg.kosmos.local" - node.override["backup"]["postgresql"]["databases"]["mastodon"] = { - username: "mastodon", - password: postgresql_data_bag_item['mastodon_user_password'] - } - - include_recipe "backup" -end diff --git a/site-cookbooks/kosmos-mastodon/recipes/build.rb b/site-cookbooks/kosmos-mastodon/recipes/build.rb new file mode 100644 index 0000000..839802f --- /dev/null +++ b/site-cookbooks/kosmos-mastodon/recipes/build.rb @@ -0,0 +1,193 @@ +# +# Cookbook Name:: kosmos-mastodon +# Recipe:: build +# +# Checks out and builds the application without running migrations or starting +# any services, so a deployment can be pre-staged before the maintenance +# window. The build is skipped while the checked-out revision is unchanged. +# + +require 'uri' + +postgresql_credentials = data_bag_item('credentials', 'postgresql') + +mastodon_path = node["kosmos-mastodon"]["directory"] +mastodon_user = "mastodon" +mastodon_home = "/home/#{mastodon_user}" + +ruby_version = node["kosmos-mastodon"]["ruby_version"] +ruby_path = "/opt/ruby_build/builds/#{ruby_version}" + +# External Redis cluster (see the kosmos_redis cookbook) +redis_host = search(:node, "role:#{node['kosmos-mastodon']['redis_server_role']}").first&.dig("knife_zero", "host") + +if redis_host.nil? + Chef::Log.fatal("No node found with '#{node['kosmos-mastodon']['redis_server_role']}' role. Stopping here.") + return +end + +redis_password = URI.encode_www_form_component(data_bag_item('credentials', 'redis')['password']) +redis_url = "redis://:#{redis_password}@#{redis_host}:#{node['kosmos-mastodon']['redis_port']}/#{node['kosmos-mastodon']['redis_db']}" + +rails_env = node.chef_environment == "development" ? "development" : "production" +deploy_env = { + # FIXME: /usr/bin was missing from PATH when running `yarn install` + "PATH" => "#{ruby_path}/bin:/usr/bin:$PATH", + "HOME" => mastodon_home, + "RAILS_ENV" => rails_env, + "NODE_ENV" => rails_env, + "COREPACK_ENABLE_DOWNLOAD_PROMPT" => "0" +} + +# Skip the build while the checked-out revision is unchanged +build_uptodate = "test -f #{mastodon_path}/tmp/built-revision && " \ + "test \"$(cat #{mastodon_path}/tmp/built-revision)\" = \"$(git -C #{mastodon_path} rev-parse HEAD)\"" + +credentials = data_bag_item('credentials', 'mastodon') + +# The repository is private; clone it with a read-only SSH deploy key kept in +# the mastodon user's home (outside the clone destination). +directory "#{mastodon_home}/.ssh" do + owner mastodon_user + group mastodon_user + mode "0700" +end + +file "#{mastodon_home}/.ssh/id_ed25519" do + # OpenSSH's PEM parser rejects a private key without a trailing newline + content lazy { credentials["repo_deploy_key"].to_s.chomp + "\n" } + owner mastodon_user + group mastodon_user + mode "0600" + sensitive true +end + +file "#{mastodon_home}/.ssh/known_hosts" do + content "#{node['kosmos-mastodon']['gitea_ssh_host_key']}\n" + owner mastodon_user + group mastodon_user + mode "0644" +end + +file "#{mastodon_home}/.ssh/config" do + content <<-EOF +Host gitea.kosmos.org + IdentityFile #{mastodon_home}/.ssh/id_ed25519 + IdentitiesOnly yes + StrictHostKeyChecking yes + UserKnownHostsFile #{mastodon_home}/.ssh/known_hosts + EOF + owner mastodon_user + group mastodon_user + mode "0600" +end + +# Chef's git resource silently skips cloning when the destination is non-empty +# and not a git clone, so make sure we start from a clean directory. +execute "clear non-git repository directory" do + command "find #{mastodon_path} -mindepth 1 -delete" + only_if { ::Dir.exist?(mastodon_path) && !::File.exist?("#{mastodon_path}/.git") } +end + +git mastodon_path do + user mastodon_user + group mastodon_user + + repository node["kosmos-mastodon"]["repo"] + revision node["kosmos-mastodon"]["revision"] + environment "GIT_SSH_COMMAND" => + "ssh -i #{mastodon_home}/.ssh/id_ed25519 -o IdentitiesOnly=yes " \ + "-o StrictHostKeyChecking=yes -o UserKnownHostsFile=#{mastodon_home}/.ssh/known_hosts" +end + +ldap_config = { + host: "ldap.kosmos.local", + port: 389, + method: "plain", + base: "ou=kosmos.org,cn=users,dc=kosmos,dc=org", + bind_dn: credentials["ldap_bind_dn"], + password: credentials["ldap_password"], + uid: "cn", + mail: "mail", + search_filter: "(&(|(cn=%{email})(mail=%{email}))(serviceEnabled=mastodon))", + uid_conversion_enabled: "true", + uid_conversion_search: "-", + uid_conversion_replace: "_" +} + +template "#{mastodon_path}/.env.#{rails_env}" do + source "env.erb" + mode "0640" + owner mastodon_user + group mastodon_user + sensitive true + variables redis_url: redis_url, + domain: node["kosmos-mastodon"]["domain"], + alternate_domains: node["kosmos-mastodon"]["alternate_domains"], + active_record_encryption_deterministic_key: credentials["active_record_encryption_deterministic_key"], + active_record_encryption_key_derivation_salt: credentials["active_record_encryption_key_derivation_salt"], + active_record_encryption_primary_key: credentials["active_record_encryption_primary_key"], + paperclip_secret: credentials['paperclip_secret'], + secret_key_base: credentials['secret_key_base'], + ldap: ldap_config, + smtp_login: credentials['smtp_user_name'], + smtp_password: credentials['smtp_password'], + smtp_from_address: "mail@#{node['kosmos-mastodon']['domain']}", + s3_endpoint: node["kosmos-mastodon"]["s3_endpoint"], + s3_region: node["kosmos-mastodon"]["s3_region"], + s3_bucket: node["kosmos-mastodon"]["s3_bucket"], + s3_alias_host: node["kosmos-mastodon"]["s3_alias_host"], + aws_access_key_id: credentials['s3_key_id'], + aws_secret_access_key: credentials['s3_secret_key'], + vapid_private_key: credentials['vapid_private_key'], + vapid_public_key: credentials['vapid_public_key'], + db_pass: postgresql_credentials['mastodon_user_password'], + db_host: "pg.kosmos.local", + sso_account_sign_up_url: node["kosmos-mastodon"]["sso_account_sign_up_url"], + sso_account_reset_password_url: node["kosmos-mastodon"]["sso_account_reset_password_url"], + sso_account_resend_confirmation_url: node["kosmos-mastodon"]["sso_account_resend_confirmation_url"], + default_locale: node["kosmos-mastodon"]["default_locale"], + force_default_locale: node["kosmos-mastodon"]["force_default_locale"], + disable_email_subscriptions: node["kosmos-mastodon"]["disable_email_subscriptions"], + allowed_private_addresses: node["kosmos-mastodon"]["allowed_private_addresses"], + libre_translate_endpoint: node["kosmos-mastodon"]["libre_translate_endpoint"] + notifies :run, "execute[restart mastodon services]", :delayed if node["kosmos-mastodon"]["deploy"] +end + +execute "bundle install" do + environment deploy_env.merge("BUNDLE_BUILD__CHARLOCK_HOLMES" => "--with-cxxflags=-std=c++17") + user mastodon_user + cwd mastodon_path + command "bundle config set --local deployment true && " \ + "bundle config set --local without 'development test' && " \ + "bundle install" + not_if build_uptodate +end + +execute "yarn install" do + environment deploy_env + user mastodon_user + cwd mastodon_path + command "yarn install --immutable" + not_if build_uptodate +end + +execute "rake assets:precompile" do + environment deploy_env + user mastodon_user + group mastodon_user + cwd mastodon_path + command "bundle exec rake assets:precompile" + not_if build_uptodate + notifies :run, "execute[record built revision]", :immediately +end + +# Record the built revision. Runs as the mastodon user so git accepts the repo +# (a root-run `git` would refuse due to dubious ownership). +execute "record built revision" do + user mastodon_user + group mastodon_user + cwd mastodon_path + command "git rev-parse HEAD > tmp/built-revision" + action :nothing +end diff --git a/site-cookbooks/kosmos-mastodon/recipes/default.rb b/site-cookbooks/kosmos-mastodon/recipes/default.rb index f6e7e0a..9401dd8 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/default.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/default.rb @@ -3,316 +3,10 @@ # Recipe:: default # -node.override["kosmos_nodejs"]["version"] = "18.20.8" +include_recipe "kosmos-mastodon::dependencies" -include_recipe "kosmos-nodejs" -include_recipe "java" -include_recipe 'redisio::default' -include_recipe 'redisio::enable' -include_recipe 'firewall' +# Check out and build the application without touching the database. +include_recipe "kosmos-mastodon::build" if node["kosmos-mastodon"]["build"] || node["kosmos-mastodon"]["deploy"] -elasticsearch_user 'elasticsearch' - -elasticsearch_install 'elasticsearch' do - type 'package' - # The current version of the elasticsearch cookbook doesn't like versions - # it doesn't know about. This would still be installing the default (7.17.9) - # on a new machine, but it doesn't upgrade the package - download_url 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.7-amd64.deb' - # SHA256 - download_checksum '5c588d779023672ba4e315e7cd4db068ac60a38873a35973574a1cae858c2030' - action :install -end - -elasticsearch_configure 'elasticsearch' do - allocated_memory node["kosmos-mastodon"]["elasticsearch"]["allocated_memory"] - - jvm_options %w( - -XX:+AlwaysPreTouch - -server - -Xss1m - -Djava.awt.headless=true - -Dfile.encoding=UTF-8 - -Djna.nosys=true - -XX:-OmitStackTraceInFastThrow - -Dio.netty.noUnsafe=true - -Dio.netty.noKeySetOptimization=true - -Dio.netty.recycler.maxCapacityPerThread=0 - -XX:+HeapDumpOnOutOfMemoryError - ) -end - -elasticsearch_service 'elasticsearch' - -postgresql_credentials = 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 - -user mastodon_user do - comment "mastodon user" - uid 62786 - gid 62786 - shell "/bin/bash" - home mastodon_path -end - -package %w(build-essential imagemagick ffmpeg libxml2-dev libxslt1-dev file git - curl pkg-config libprotobuf-dev protobuf-compiler libidn11 - libidn11-dev libjemalloc2 libpq-dev) - -ruby_version = "3.3.5" - -ruby_path = "/opt/ruby_build/builds/#{ruby_version}" -bundle_path = "#{ruby_path}/bin/bundle" - -ruby_build_install 'v20231025' -ruby_build_definition ruby_version do - prefix_path ruby_path -end - -execute "systemctl daemon-reload" do - command "systemctl daemon-reload" - action :nothing -end - -# mastodon-web service -# -template "/lib/systemd/system/mastodon-web.service" do - source "mastodon-web.systemd.service.erb" - variables user: mastodon_user, - app_dir: mastodon_path, - bind: bind_ip, - port: node["kosmos-mastodon"]["app_port"], - bundle_path: bundle_path - notifies :run, "execute[systemctl daemon-reload]", :immediately - notifies :restart, "service[mastodon-web]", :delayed -end - -# mastodon-sidekiq service -# -template "/lib/systemd/system/mastodon-sidekiq.service" do - source "mastodon-sidekiq.systemd.service.erb" - variables user: mastodon_user, - app_dir: mastodon_path, - bundle_path: bundle_path, - sidekiq_threads: node["kosmos-mastodon"]["sidekiq_threads"] - notifies :run, "execute[systemctl daemon-reload]", :immediately - notifies :restart, "service[mastodon-sidekiq]", :delayed -end - -# mastodon-sidekiq-scheduler service -# -template "/lib/systemd/system/mastodon-sidekiq-scheduler.service" do - source "mastodon-sidekiq-scheduler.systemd.service.erb" - variables user: mastodon_user, - app_dir: mastodon_path, - bundle_path: bundle_path, - sidekiq_threads: 1 - notifies :run, "execute[systemctl daemon-reload]", :immediately - notifies :restart, "service[mastodon-sidekiq-scheduler]", :delayed -end - -# mastodon-streaming service -# -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 -end - -rails_env = node.chef_environment == "development" ? "development" : "production" -deploy_env = { - # FIXME: /usr/bin was missing from PATH when running `yarn install` - "PATH" => "#{ruby_path}/bin:/usr/bin:$PATH", - "HOME" => mastodon_path, - "RAILS_ENV" => rails_env, - "NODE_ENV" => rails_env, - "SKIP_POST_DEPLOYMENT_MIGRATIONS" => "true" -} - -git mastodon_path do - user mastodon_user - group mastodon_user - - repository node["kosmos-mastodon"]["repo"] - revision node["kosmos-mastodon"]["revision"] - # Restart services on deployments - notifies :run, "execute[restart mastodon services]", :delayed -end - -execute "restart mastodon services" do - command "true" - action :nothing - notifies :restart, "service[mastodon-web]", :delayed - notifies :restart, "service[mastodon-sidekiq]", :delayed - notifies :restart, "service[mastodon-sidekiq-scheduler]", :delayed - notifies :restart, "service[mastodon-streaming]", :delayed -end - -credentials = data_bag_item('credentials', 'mastodon') - -ldap_config = { - host: "ldap.kosmos.local", - port: 389, - method: "plain", - base: "ou=kosmos.org,cn=users,dc=kosmos,dc=org", - bind_dn: credentials["ldap_bind_dn"], - password: credentials["ldap_password"], - uid: "cn", - mail: "mail", - search_filter: "(&(|(cn=%{email})(mail=%{email}))(serviceEnabled=mastodon))", - uid_conversion_enabled: "true", - uid_conversion_search: "-", - uid_conversion_replace: "_" -} - -template "#{mastodon_path}/.env.#{rails_env}" do - source "env.erb" - mode "0640" - owner mastodon_user - group mastodon_user - sensitive true - variables redis_url: node["kosmos-mastodon"]["redis_url"], - domain: node["kosmos-mastodon"]["domain"], - alternate_domains: node["kosmos-mastodon"]["alternate_domains"], - active_record_encryption_deterministic_key: credentials["active_record_encryption_deterministic_key"], - active_record_encryption_key_derivation_salt: credentials["active_record_encryption_key_derivation_salt"], - active_record_encryption_primary_key: credentials["active_record_encryption_primary_key"], - paperclip_secret: credentials['paperclip_secret'], - secret_key_base: credentials['secret_key_base'], - otp_secret: credentials['otp_secret'], - ldap: ldap_config, - smtp_login: credentials['smtp_user_name'], - smtp_password: credentials['smtp_password'], - smtp_from_address: "mail@#{node['kosmos-mastodon']['domain']}", - s3_endpoint: node["kosmos-mastodon"]["s3_endpoint"], - s3_region: node["kosmos-mastodon"]["s3_region"], - s3_bucket: node["kosmos-mastodon"]["s3_bucket"], - s3_alias_host: node["kosmos-mastodon"]["s3_alias_host"], - aws_access_key_id: credentials['s3_key_id'], - aws_secret_access_key: credentials['s3_secret_key'], - vapid_private_key: credentials['vapid_private_key'], - vapid_public_key: credentials['vapid_public_key'], - db_pass: postgresql_credentials['mastodon_user_password'], - db_host: "pg.kosmos.local", - sso_account_sign_up_url: node["kosmos-mastodon"]["sso_account_sign_up_url"], - sso_account_reset_password_url: node["kosmos-mastodon"]["sso_account_reset_password_url"], - sso_account_resend_confirmation_url: node["kosmos-mastodon"]["sso_account_resend_confirmation_url"], - default_locale: node["kosmos-mastodon"]["default_locale"], - allowed_private_addresses: node["kosmos-mastodon"]["allowed_private_addresses"], - libre_translate_endpoint: node["kosmos-mastodon"]["libre_translate_endpoint"] - notifies :run, "execute[restart mastodon services]", :delayed -end - -execute "bundle install" do - environment deploy_env - user mastodon_user - cwd mastodon_path - command "bundle install --without development,test --deployment" -end - -execute "yarn install" do - environment deploy_env - user mastodon_user - cwd mastodon_path - command "corepack prepare && yarn install --immutable" -end - -execute "rake assets:precompile" do - environment deploy_env - user mastodon_user - group mastodon_user - cwd mastodon_path - command "bundle exec rake assets:precompile" -end - -execute "rake db:migrate" do - environment deploy_env - user mastodon_user - group mastodon_user - cwd mastodon_path - command "bundle exec rake db:migrate" -end - -service "mastodon-web" do - action [:enable, :start] -end - -service "mastodon-sidekiq" do - action [:enable, :start] -end - -service "mastodon-sidekiq-scheduler" do - action [:enable, :start] -end - -service "mastodon-streaming" do - action [:enable, :start] -end - -# -# Delete cached remote media older than 30 days -# Will be re-fetched if necessary -# - -systemd_unit 'mastodon-delete-old-media-cache.service' do - content({ - Unit: { - Description: 'Delete old Mastodon media cache' - }, - Service: { - Type: "oneshot", - WorkingDirectory: mastodon_path, - Environment: "RAILS_ENV=#{rails_env}", - ExecStart: "#{bundle_path} exec bin/tootctl media remove --days 30", - } - }) - triggers_reload true - action [:create] -end - -systemd_unit 'mastodon-delete-old-media-cache.timer' do - content({ - Unit: { - Description: 'Delete old Mastodon media cache' - }, - Timer: { - OnCalendar: '*-*-* 00:00:00', - Persistent: 'true' - }, - Install: { - WantedBy: 'timer.target' - } - }) - triggers_reload true - action [:create, :enable, :start] -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 +# Run migrations and manage the services. +include_recipe "kosmos-mastodon::deploy" if node["kosmos-mastodon"]["deploy"] diff --git a/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb new file mode 100644 index 0000000..a71f1cd --- /dev/null +++ b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb @@ -0,0 +1,97 @@ +# +# Cookbook Name:: kosmos-mastodon +# Recipe:: dependencies +# +# Installs everything Mastodon needs to run, without deploying or starting the +# application itself. Can be run ahead of a deployment to shorten downtime. +# + +node.override["kosmos_nodejs"]["version"] = node["kosmos-mastodon"]["nodejs_version"] + +include_recipe "kosmos-nodejs" +include_recipe "firewall" + +elasticsearch_user 'elasticsearch' + +# Elasticsearch 7.x ships a bundled JDK and no JAVA_HOME is configured, so no +# system Java is needed (the java cookbook's openjdk recipe no longer supports +# Ubuntu 24.04 anyway). +elasticsearch_install 'elasticsearch' do + type 'package' + # The current version of the elasticsearch cookbook doesn't like versions + # it doesn't know about. This would still be installing the default (7.17.9) + # on a new machine, but it doesn't upgrade the package + download_url 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.7-amd64.deb' + # SHA256 + download_checksum '5c588d779023672ba4e315e7cd4db068ac60a38873a35973574a1cae858c2030' + action :install +end + +elasticsearch_configure 'elasticsearch' do + allocated_memory node["kosmos-mastodon"]["elasticsearch"]["allocated_memory"] + + jvm_options %w( + -XX:+AlwaysPreTouch + -server + -Xss1m + -Djava.awt.headless=true + -Dfile.encoding=UTF-8 + -Djna.nosys=true + -XX:-OmitStackTraceInFastThrow + -Dio.netty.noUnsafe=true + -Dio.netty.noKeySetOptimization=true + -Dio.netty.recycler.maxCapacityPerThread=0 + -XX:+HeapDumpOnOutOfMemoryError + ) +end + +elasticsearch_service 'elasticsearch' + +mastodon_path = node["kosmos-mastodon"]["directory"] +mastodon_user = "mastodon" +mastodon_home = "/home/#{mastodon_user}" + +group mastodon_user do + gid 62786 +end + +user mastodon_user do + comment "mastodon user" + uid 62786 + gid 62786 + shell "/bin/bash" + home mastodon_home +end + +directory mastodon_home do + owner mastodon_user + group mastodon_user + mode "0755" +end + +directory mastodon_path do + owner mastodon_user + group mastodon_user + mode "0755" +end + +# Mastodon 4.6 dropped ImageMagick in favor of libvips and requires libvips >= 8.13 +package %w(build-essential ffmpeg libxml2-dev libxslt1-dev file git + curl pkg-config libprotobuf-dev protobuf-compiler libidn-dev + libjemalloc2 libpq-dev libvips-dev) + +ruby_version = node["kosmos-mastodon"]["ruby_version"] + +ruby_path = "/opt/ruby_build/builds/#{ruby_version}" + +ruby_build_install node["kosmos-mastodon"]["ruby_build_version"] +ruby_build_definition ruby_version do + prefix_path ruby_path +end + +# Node 24 ships corepack >= 0.30, for which `corepack prepare` without an +# argument is no longer valid. Enable the shims as root and let yarn pick up +# the version pinned in package.json instead. +execute "corepack enable" do + command "corepack enable" +end diff --git a/site-cookbooks/kosmos-mastodon/recipes/deploy.rb b/site-cookbooks/kosmos-mastodon/recipes/deploy.rb new file mode 100644 index 0000000..9574eea --- /dev/null +++ b/site-cookbooks/kosmos-mastodon/recipes/deploy.rb @@ -0,0 +1,240 @@ +# +# Cookbook Name:: kosmos-mastodon +# Recipe:: deploy +# +# Runs database migrations and manages the services. Requires the application +# to have been checked out and built by kosmos-mastodon::build. Migrations and +# the service restart run once per checked-out revision. +# + +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 + +ruby_version = node["kosmos-mastodon"]["ruby_version"] +ruby_path = "/opt/ruby_build/builds/#{ruby_version}" +bundle_path = "#{ruby_path}/bin/bundle" + +rails_env = node.chef_environment == "development" ? "development" : "production" +deploy_env = { + # FIXME: /usr/bin was missing from PATH when running `yarn install` + "PATH" => "#{ruby_path}/bin:/usr/bin:$PATH", + "HOME" => "/home/#{mastodon_user}", + "RAILS_ENV" => rails_env, + "NODE_ENV" => rails_env, + "COREPACK_ENABLE_DOWNLOAD_PROMPT" => "0" +} + +# Run migrations, restart services and (re)build the search index once per +# checked-out revision, regardless of whether the code was pre-built. +deploy_uptodate = "test -f #{mastodon_path}/tmp/deployed-revision && " \ + "test \"$(cat #{mastodon_path}/tmp/deployed-revision)\" = \"$(git -C #{mastodon_path} rev-parse HEAD)\"" + +execute "systemctl daemon-reload" do + command "systemctl daemon-reload" + action :nothing +end + +# mastodon-web service +# +template "/lib/systemd/system/mastodon-web.service" do + source "mastodon-web.systemd.service.erb" + variables user: mastodon_user, + app_dir: mastodon_path, + bind: bind_ip, + port: node["kosmos-mastodon"]["app_port"], + bundle_path: bundle_path + notifies :run, "execute[systemctl daemon-reload]", :immediately + notifies :restart, "service[mastodon-web]", :delayed +end + +# mastodon-sidekiq service +# +template "/lib/systemd/system/mastodon-sidekiq.service" do + source "mastodon-sidekiq.systemd.service.erb" + variables user: mastodon_user, + app_dir: mastodon_path, + bundle_path: bundle_path, + sidekiq_threads: node["kosmos-mastodon"]["sidekiq_threads"] + notifies :run, "execute[systemctl daemon-reload]", :immediately + notifies :restart, "service[mastodon-sidekiq]", :delayed +end + +# mastodon-sidekiq-scheduler service +# +template "/lib/systemd/system/mastodon-sidekiq-scheduler.service" do + source "mastodon-sidekiq-scheduler.systemd.service.erb" + variables user: mastodon_user, + app_dir: mastodon_path, + bundle_path: bundle_path, + sidekiq_threads: 1 + notifies :run, "execute[systemctl daemon-reload]", :immediately + notifies :restart, "service[mastodon-sidekiq-scheduler]", :delayed +end + +# mastodon-streaming service +# +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 +end + +execute "restart mastodon services" do + command "systemctl restart mastodon-web mastodon-sidekiq mastodon-sidekiq-scheduler mastodon-streaming" + action :nothing +end + +# Populate the Elasticsearch indices in the background. The indices and +# mappings are created synchronously during the deployment; this unit only does +# the (potentially very long) import, so it is started without blocking. Declared +# before the migration chain below because that chain starts it. +systemd_unit 'mastodon-search-deploy.service' do + content({ + Unit: { + Description: 'Populate the Mastodon search index' + }, + Service: { + Type: "oneshot", + User: mastodon_user, + WorkingDirectory: mastodon_path, + Environment: "RAILS_ENV=#{rails_env}", + ExecStart: "#{bundle_path} exec bin/tootctl search deploy", + TimeoutStartSec: "21600", + } + }) + triggers_reload true + action [:create] +end + +# Mastodon 4.4+ splits migrations into pre- and post-deployment phases. +# Pre-deployment migrations must run before the services are (re)started. +execute "rake db:migrate (pre-deployment)" do + environment deploy_env.merge("SKIP_POST_DEPLOYMENT_MIGRATIONS" => "true") + user mastodon_user + group mastodon_user + cwd mastodon_path + command "bundle exec rake db:migrate" + timeout 21_600 + not_if deploy_uptodate + notifies :run, "execute[restart mastodon services]", :immediately + notifies :run, "execute[rake db:migrate (post-deployment)]", :immediately +end + +execute "rake db:migrate (post-deployment)" do + environment deploy_env + user mastodon_user + group mastodon_user + cwd mastodon_path + command "bundle exec rake db:migrate" + timeout 21_600 + action :nothing + notifies :run, "execute[tootctl search deploy (create indices)]", :immediately +end + +# Create or upgrade the Elasticsearch indices and mappings without importing +# data, so that search does not fail on a missing index. The (potentially very +# long) import is deferred to a systemd unit started in the background below. +execute "tootctl search deploy (create indices)" do + environment deploy_env + user mastodon_user + group mastodon_user + cwd mastodon_path + command "#{bundle_path} exec bin/tootctl search deploy --no-import" + timeout 3_600 + action :nothing + notifies :run, "execute[start mastodon search deploy]", :immediately + notifies :run, "execute[record deployed revision]", :immediately +end + +execute "start mastodon search deploy" do + command "systemctl start --no-block mastodon-search-deploy.service" + action :nothing +end + +# Record the deployed revision. Runs as the mastodon user so git accepts the +# repo (a root-run `git` would refuse due to dubious ownership). +execute "record deployed revision" do + user mastodon_user + group mastodon_user + cwd mastodon_path + command "git rev-parse HEAD > tmp/deployed-revision" + action :nothing +end + +service "mastodon-web" do + action [:enable, :start] +end + +service "mastodon-sidekiq" do + action [:enable, :start] +end + +service "mastodon-sidekiq-scheduler" do + action [:enable, :start] +end + +service "mastodon-streaming" do + action [:enable, :start] +end + +# +# Delete cached remote media older than 30 days +# Will be re-fetched if necessary +# + +systemd_unit 'mastodon-delete-old-media-cache.service' do + content({ + Unit: { + Description: 'Delete old Mastodon media cache' + }, + Service: { + Type: "oneshot", + WorkingDirectory: mastodon_path, + Environment: "RAILS_ENV=#{rails_env}", + ExecStart: "#{bundle_path} exec bin/tootctl media remove --days 30", + } + }) + triggers_reload true + action [:create] +end + +systemd_unit 'mastodon-delete-old-media-cache.timer' do + content({ + Unit: { + Description: 'Delete old Mastodon media cache' + }, + Timer: { + OnCalendar: '*-*-* 00:00:00', + Persistent: 'true' + }, + Install: { + WantedBy: 'timer.target' + } + }) + triggers_reload true + action [:create, :enable, :start] +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/libretranslate.rb b/site-cookbooks/kosmos-mastodon/recipes/libretranslate.rb index d06557d..3cb3e49 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/libretranslate.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/libretranslate.rb @@ -5,9 +5,10 @@ build_essential -version = "1.3.8" +version = "1.9.6" +venv = "/opt/libretranslate/venv" -%w{ python3 python3-pip python3-setuptools python3-dev }.each do |pkg| +%w{ python3 python3-pip python3-setuptools python3-dev python3-venv }.each do |pkg| apt_package pkg end @@ -17,19 +18,26 @@ user "libretranslate" do manage_home true end +# LibreTranslate is installed into a dedicated virtualenv. Ubuntu 24.04's +# system Python is externally managed (PEP 668) and refuses `pip install`. +bash "create_libretranslate_venv" do + code "sudo -u libretranslate python3 -m venv #{venv}" + not_if { ::File.exist?("#{venv}/bin/pip") } +end + bash "install_libretranslate" do - code "sudo -u libretranslate pip3 install --user --prefer-binary libretranslate==#{version}" + code "sudo -u libretranslate #{venv}/bin/pip install --prefer-binary libretranslate==#{version}" action :run - not_if { `sudo -u libretranslate pip3 list |grep libretranslate`.split(' ')[1] == version rescue false } + not_if "#{venv}/bin/pip show libretranslate 2>/dev/null | grep -q 'Version: #{version}'" notifies :restart, "service[libretranslate]", :delayed end -languages = `sudo -u libretranslate /opt/libretranslate/.local/bin/argospm search` +languages = `sudo -u libretranslate #{venv}/bin/argospm search` languages.each_line do |line| lang = line.split(':').first bash "install_lt_#{lang}" do - code "sudo -u libretranslate /opt/libretranslate/.local/bin/argospm install #{lang}" + code "sudo -u libretranslate #{venv}/bin/argospm install #{lang}" action :nothing end end @@ -46,7 +54,7 @@ systemd_unit "libretranslate.service" do User: "libretranslate", Group: "libretranslate", WorkingDirectory: "/opt/libretranslate/", - ExecStart: "/opt/libretranslate/.local/bin/libretranslate --host 127.0.0.1 --port 5000 --disable-files-translation", + ExecStart: "#{venv}/bin/libretranslate --host 127.0.0.1 --port 5000 --disable-files-translation", Restart: "always" }, Install: { diff --git a/site-cookbooks/kosmos-mastodon/templates/default/env.erb b/site-cookbooks/kosmos-mastodon/templates/default/env.erb index f42a53e..1e5a7ab 100644 --- a/site-cookbooks/kosmos-mastodon/templates/default/env.erb +++ b/site-cookbooks/kosmos-mastodon/templates/default/env.erb @@ -17,7 +17,6 @@ ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=<%= @active_record_encryption_key_d ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=<%= @active_record_encryption_primary_key %> PAPERCLIP_SECRET=<%= @paperclip_secret %> SECRET_KEY_BASE=<%= @secret_key_base %> -OTP_SECRET=<%= @otp_secret %> # Registrations # Single user mode will disable registrations and redirect frontpage to the first profile @@ -74,6 +73,10 @@ AWS_SECRET_ACCESS_KEY=<%= @aws_secret_access_key %> # locale DEFAULT_LOCALE=<%= @default_locale %> +FORCE_DEFAULT_LOCALE=<%= @force_default_locale %> + +# Email subscriptions (Mastodon 4.6+) +DISABLE_EMAIL_SUBSCRIPTIONS=<%= @disable_email_subscriptions %> <% if @libre_translate_endpoint %> # translate diff --git a/site-cookbooks/kosmos-mastodon/templates/default/mastodon-sidekiq-scheduler.systemd.service.erb b/site-cookbooks/kosmos-mastodon/templates/default/mastodon-sidekiq-scheduler.systemd.service.erb index 82d36f4..4c9e995 100644 --- a/site-cookbooks/kosmos-mastodon/templates/default/mastodon-sidekiq-scheduler.systemd.service.erb +++ b/site-cookbooks/kosmos-mastodon/templates/default/mastodon-sidekiq-scheduler.systemd.service.erb @@ -1,7 +1,5 @@ [Unit] Description=mastodon-sidekiq-scheduler -Requires=redis@6379.service -After=redis@6379.service [Service] Type=simple 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 459dbea..c6646d4 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 @@ -1,7 +1,5 @@ [Unit] Description=mastodon-sidekiq -Requires=redis@6379.service -After=redis@6379.service [Service] Type=simple @@ -11,7 +9,7 @@ Environment="RAILS_ENV=production" Environment="DB_POOL=<%= @sidekiq_threads %>" Environment="MALLOC_ARENA_MAX=2" 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 -q ingress +ExecStart=<%= @bundle_path %> exec sidekiq -c <%= @sidekiq_threads %> -q default -q mailers -q pull -q push -q ingress -q fasp TimeoutSec=15 Restart=always 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 93a694e..86ce186 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 @@ -1,7 +1,5 @@ [Unit] Description=mastodon-web -Requires=redis@6379.service -After=redis@6379.service [Service] Type=simple diff --git a/site-cookbooks/liquor_cabinet/recipes/default.rb b/site-cookbooks/liquor_cabinet/recipes/default.rb index f333f69..027b2fd 100644 --- a/site-cookbooks/liquor_cabinet/recipes/default.rb +++ b/site-cookbooks/liquor_cabinet/recipes/default.rb @@ -58,7 +58,9 @@ end execute "bundle install" do user deploy_user cwd deploy_path - command "#{bundle_path} install --without development,test --deployment" + command "#{bundle_path} config set --local deployment true && " \ + "#{bundle_path} config set --local without 'development test' && " \ + "#{bundle_path} install" end template "#{deploy_path}/config.yml.erb" do