From b60ca687ec56057aa2cbe01e18f74ab370706d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 16:12:48 +0200 Subject: [PATCH 01/21] Upgrade Mastodon to 4.7 Bump the production branch to 4.7 and adjust for the changes between 4.3 and 4.7: - Node 24.21.0 and Ruby 4.0.7 (via ruby-build v20260924) - Redis 7.4.11 (Mastodon 4.5 requires >= 7.0) - Replace ImageMagick with libvips (required since 4.6) and libidn11 with libidn - Split database migrations into pre-/post-deployment phases and rebuild the Elasticsearch accounts index mappings (required since 4.4) - Remove the OTP_SECRET environment variable (removed in 4.4) - Disable email subscriptions (new optional feature in 4.6) and force the default locale (DEFAULT_LOCALE no longer overrides it since 4.4) - Add the new fasp Sidekiq queue - Enable corepack for yarn instead of the removed no-arg 'corepack prepare' --- .../kosmos-mastodon/attributes/default.rb | 10 ++- .../kosmos-mastodon/recipes/default.rb | 65 ++++++++++++++----- .../kosmos-mastodon/templates/default/env.erb | 5 +- .../mastodon-sidekiq.systemd.service.erb | 2 +- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/site-cookbooks/kosmos-mastodon/attributes/default.rb b/site-cookbooks/kosmos-mastodon/attributes/default.rb index 921f0f5..73edc79 100644 --- a/site-cookbooks/kosmos-mastodon/attributes/default.rb +++ b/site-cookbooks/kosmos-mastodon/attributes/default.rb @@ -1,5 +1,5 @@ node.default["kosmos-mastodon"]["repo"] = "https://gitea.kosmos.org/kosmos/mastodon.git" -node.default["kosmos-mastodon"]["revision"] = "production-4.3" +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 @@ -25,6 +25,12 @@ 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" +node.override["redisio"]["version"] = "7.4.11" diff --git a/site-cookbooks/kosmos-mastodon/recipes/default.rb b/site-cookbooks/kosmos-mastodon/recipes/default.rb index f6e7e0a..29df8b3 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/default.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/default.rb @@ -3,7 +3,7 @@ # Recipe:: default # -node.override["kosmos_nodejs"]["version"] = "18.20.8" +node.override["kosmos_nodejs"]["version"] = "24.21.0" include_recipe "kosmos-nodejs" include_recipe "java" @@ -67,16 +67,17 @@ user mastodon_user do 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) +# 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 = "3.3.5" +ruby_version = "4.0.7" ruby_path = "/opt/ruby_build/builds/#{ruby_version}" bundle_path = "#{ruby_path}/bin/bundle" -ruby_build_install 'v20231025' +ruby_build_install 'v20260924' ruby_build_definition ruby_version do prefix_path ruby_path end @@ -142,7 +143,7 @@ deploy_env = { "HOME" => mastodon_path, "RAILS_ENV" => rails_env, "NODE_ENV" => rails_env, - "SKIP_POST_DEPLOYMENT_MIGRATIONS" => "true" + "COREPACK_ENABLE_DOWNLOAD_PROMPT" => "0" } git mastodon_path do @@ -151,17 +152,14 @@ git mastodon_path do repository node["kosmos-mastodon"]["repo"] revision node["kosmos-mastodon"]["revision"] - # Restart services on deployments + # Restart services (and run post-deployment migrations) on deployments notifies :run, "execute[restart mastodon services]", :delayed end execute "restart mastodon services" do - command "true" + command "systemctl restart mastodon-web mastodon-sidekiq mastodon-sidekiq-scheduler mastodon-streaming" 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 + notifies :run, "execute[rake db:migrate (post-deployment)]", :immediately end credentials = data_bag_item('credentials', 'mastodon') @@ -195,7 +193,6 @@ template "#{mastodon_path}/.env.#{rails_env}" do 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'], @@ -214,23 +211,32 @@ template "#{mastodon_path}/.env.#{rails_env}" do 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 end execute "bundle install" do - environment deploy_env + environment deploy_env.merge("BUNDLE_BUILD__CHARLOCK_HOLMES" => "--with-cxxflags=-std=c++17") user mastodon_user cwd mastodon_path command "bundle install --without development,test --deployment" 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 + execute "yarn install" do environment deploy_env user mastodon_user cwd mastodon_path - command "corepack prepare && yarn install --immutable" + command "yarn install --immutable" end execute "rake assets:precompile" do @@ -241,12 +247,37 @@ execute "rake assets:precompile" do command "bundle exec rake assets:precompile" end -execute "rake db:migrate" do +# 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 +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 accounts mapping]", :immediately +end + +# Mastodon 4.4 changed the Elasticsearch `accounts` index mappings +execute "tootctl search deploy accounts mapping" do + environment deploy_env + user mastodon_user + group mastodon_user + cwd mastodon_path + command "#{bundle_path} exec bin/tootctl search deploy --only-mapping --only=accounts" + timeout 3_600 + action :nothing end service "mastodon-web" do 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.systemd.service.erb b/site-cookbooks/kosmos-mastodon/templates/default/mastodon-sidekiq.systemd.service.erb index 459dbea..19452da 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 @@ -11,7 +11,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 From 735145f5a98af960ee8412b8d9a8a85ac49cfb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 16:13:53 +0200 Subject: [PATCH 02/21] Add mastodon-4 node and client config --- clients/mastodon-4.json | 4 ++++ nodes | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 clients/mastodon-4.json 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/nodes b/nodes index b3b5042..3ba87a9 160000 --- a/nodes +++ b/nodes @@ -1 +1 @@ -Subproject commit b3b5042655b1f9f6c41e4ec46da1ed574200e05b +Subproject commit 3ba87a93734e4cc7afa975be60d4cd5915ca0fd2 From 591d6dc4ec49b4b823e39e2e2d310d313dc0516d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 16:41:28 +0200 Subject: [PATCH 03/21] Split Mastodon runtime deps from deployment, use Redis cluster Add a kosmos-mastodon::dependencies recipe with everything needed to run Mastodon (Node, Ruby, libvips, Elasticsearch, packages) but without the app deployment. The default recipe includes it and only runs the deployment when node['kosmos-mastodon']['deploy'] is true, so a VM can be prepared ahead of a maintenance window. Stop using the local redisio instance and connect to the external Redis cluster (redis_server role, db 2, password from the credentials data bag) instead. Remove the redisio service dependencies from the systemd units and drop the redisio cookbook dependency. --- .../kosmos-mastodon/attributes/default.rb | 17 +++- site-cookbooks/kosmos-mastodon/metadata.rb | 1 - .../kosmos-mastodon/recipes/default.rb | 84 ++++--------------- .../kosmos-mastodon/recipes/dependencies.rb | 82 ++++++++++++++++++ ...odon-sidekiq-scheduler.systemd.service.erb | 2 - .../mastodon-sidekiq.systemd.service.erb | 2 - .../default/mastodon-web.systemd.service.erb | 2 - 7 files changed, 112 insertions(+), 78 deletions(-) create mode 100644 site-cookbooks/kosmos-mastodon/recipes/dependencies.rb diff --git a/site-cookbooks/kosmos-mastodon/attributes/default.rb b/site-cookbooks/kosmos-mastodon/attributes/default.rb index 73edc79..237c46e 100644 --- a/site-cookbooks/kosmos-mastodon/attributes/default.rb +++ b/site-cookbooks/kosmos-mastodon/attributes/default.rb @@ -6,8 +6,21 @@ 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 + +# 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" + +# 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 @@ -32,5 +45,3 @@ node.default["kosmos-mastodon"]["force_default_locale"] = true # 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"] = "7.4.11" diff --git a/site-cookbooks/kosmos-mastodon/metadata.rb b/site-cookbooks/kosmos-mastodon/metadata.rb index 1f31d47..4e91875 100644 --- a/site-cookbooks/kosmos-mastodon/metadata.rb +++ b/site-cookbooks/kosmos-mastodon/metadata.rb @@ -10,7 +10,6 @@ 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/default.rb b/site-cookbooks/kosmos-mastodon/recipes/default.rb index 29df8b3..75f04ba 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/default.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/default.rb @@ -3,46 +3,13 @@ # Recipe:: default # -node.override["kosmos_nodejs"]["version"] = "24.21.0" +include_recipe "kosmos-mastodon::dependencies" -include_recipe "kosmos-nodejs" -include_recipe "java" -include_recipe 'redisio::default' -include_recipe 'redisio::enable' -include_recipe 'firewall' +# The rest is the actual Mastodon deployment. Skip it when only the runtime +# dependencies should be installed (e.g. to pre-stage a new VM). +return unless 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' +require 'uri' postgresql_credentials = data_bag_item('credentials', 'postgresql') @@ -55,33 +22,21 @@ bind_ip = if node.chef_environment == "production" 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 - -# 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 = "4.0.7" - +ruby_version = node["kosmos-mastodon"]["ruby_version"] ruby_path = "/opt/ruby_build/builds/#{ruby_version}" bundle_path = "#{ruby_path}/bin/bundle" -ruby_build_install 'v20260924' -ruby_build_definition ruby_version do - prefix_path ruby_path +# 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']}" + execute "systemctl daemon-reload" do command "systemctl daemon-reload" action :nothing @@ -185,7 +140,7 @@ template "#{mastodon_path}/.env.#{rails_env}" do owner mastodon_user group mastodon_user sensitive true - variables redis_url: node["kosmos-mastodon"]["redis_url"], + 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"], @@ -225,13 +180,6 @@ execute "bundle install" do command "bundle install --without development,test --deployment" 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 - execute "yarn install" do environment deploy_env user mastodon_user diff --git a/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb new file mode 100644 index 0000000..6cec72f --- /dev/null +++ b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb @@ -0,0 +1,82 @@ +# +# 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 "java" +include_recipe "firewall" + +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' + +mastodon_path = node["kosmos-mastodon"]["directory"] +mastodon_user = "mastodon" + +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 + +# 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/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 19452da..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 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 From fef57f1ee15b8e6bca66c9299c7a475da48c4354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 16:41:40 +0200 Subject: [PATCH 04/21] Update mastodon-4 node to dependency-only deployment --- nodes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes b/nodes index 3ba87a9..992f643 160000 --- a/nodes +++ b/nodes @@ -1 +1 @@ -Subproject commit 3ba87a93734e4cc7afa975be60d4cd5915ca0fd2 +Subproject commit 992f6434263e231ce510837df77df22c3b5c5d8f From 9fe5def5f9924df2f239bef059e2c30a991c3d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 16:51:18 +0200 Subject: [PATCH 05/21] Move Mastodon deploy flag to the mastodon role Set kosmos-mastodon.deploy to false as a role default, making it overridable per node, and drop the node-level attribute. --- nodes | 2 +- roles/mastodon.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nodes b/nodes index 992f643..d730967 160000 --- a/nodes +++ b/nodes @@ -1 +1 @@ -Subproject commit 992f6434263e231ce510837df77df22c3b5c5d8f +Subproject commit d730967916674ffde7d1e00da792ecda0cdf0b84 diff --git a/roles/mastodon.rb b/roles/mastodon.rb index b35d2b8..4b202c7 100644 --- a/roles/mastodon.rb +++ b/roles/mastodon.rb @@ -1,5 +1,9 @@ name "mastodon" +default_attributes 'kosmos-mastodon' => { + 'deploy' => false +} + run_list %w( role[postgresql_client] kosmos-mastodon::libretranslate From 8a11cd88f71bb59699c6a321cdab8fb438a1fc5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 17:17:27 +0200 Subject: [PATCH 06/21] Create search indices during deploy, import in background Mastodon 4.4+ can create/upgrade the Elasticsearch indices and mappings without importing data. Do that synchronously during the deployment so search does not fail on a missing index, then populate the (potentially very long) import via a systemd unit started with --no-block so the maintenance window is not extended. --- .../kosmos-mastodon/recipes/default.rb | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/site-cookbooks/kosmos-mastodon/recipes/default.rb b/site-cookbooks/kosmos-mastodon/recipes/default.rb index 75f04ba..c96e242 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/default.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/default.rb @@ -214,18 +214,26 @@ execute "rake db:migrate (post-deployment)" do command "bundle exec rake db:migrate" timeout 21_600 action :nothing - notifies :run, "execute[tootctl search deploy accounts mapping]", :immediately + notifies :run, "execute[tootctl search deploy (create indices)]", :immediately end -# Mastodon 4.4 changed the Elasticsearch `accounts` index mappings -execute "tootctl search deploy accounts mapping" do +# 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 --only-mapping --only=accounts" + command "#{bundle_path} exec bin/tootctl search deploy --no-import" timeout 3_600 action :nothing + notifies :run, "execute[start mastodon search deploy]", :immediately +end + +execute "start mastodon search deploy" do + command "systemctl start --no-block mastodon-search-deploy.service" + action :nothing end service "mastodon-web" do @@ -282,6 +290,30 @@ systemd_unit 'mastodon-delete-old-media-cache.timer' do action [:create, :enable, :start] end +# +# Populate the Elasticsearch indices in the background. The indices and +# mappings are created synchronously by the recipe above; this unit only does +# the (potentially very long) import, so it is started without blocking. +# + +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 + firewall_rule "mastodon_app" do port node['kosmos-mastodon']['app_port'] source "10.1.1.0/24" From b09808b12e062e54f89e62d6874a3793fc636c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 17:26:40 +0200 Subject: [PATCH 07/21] Drop the java cookbook dependency The java cookbook's openjdk recipe unconditionally adds the dead openjdk-r PPA on Ubuntu and cannot be told not to, which breaks on Ubuntu 24.04. Elasticsearch 7.x ships a bundled JDK and no JAVA_HOME is configured, so no system Java is needed. --- site-cookbooks/kosmos-mastodon/metadata.rb | 1 - site-cookbooks/kosmos-mastodon/recipes/dependencies.rb | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/site-cookbooks/kosmos-mastodon/metadata.rb b/site-cookbooks/kosmos-mastodon/metadata.rb index 4e91875..4e9883f 100644 --- a/site-cookbooks/kosmos-mastodon/metadata.rb +++ b/site-cookbooks/kosmos-mastodon/metadata.rb @@ -8,7 +8,6 @@ version '0.2.1' depends 'backup' depends 'elasticsearch' -depends 'java' depends 'firewall' depends 'postgresql' depends 'kosmos-nodejs' diff --git a/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb index 6cec72f..f0ff8a0 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb @@ -9,11 +9,13 @@ node.override["kosmos_nodejs"]["version"] = node["kosmos-mastodon"]["nodejs_version"] include_recipe "kosmos-nodejs" -include_recipe "java" 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 From ec2645c5da7ec8bf70727a41ac9a9e254219e9c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 17:44:16 +0200 Subject: [PATCH 08/21] Install LibreTranslate in a virtualenv Ubuntu 24.04's system Python is externally managed (PEP 668) and refuses pip installs. Also bump LibreTranslate from 1.3.8 to 1.9.6, since the former pulls ctranslate2 2.24.0 which has no Python 3.12 wheels. --- .../kosmos-mastodon/recipes/libretranslate.rb | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) 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: { From 9dfdf6bc9b3806e8a73caf93f82d92c7c10f1767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 17:53:57 +0200 Subject: [PATCH 09/21] Remove the backup gem from Mastodon The shared backup cookbook hardcodes postgresql-client-12, which does not exist on Ubuntu 24.04. Drop the Mastodon backup recipe and its role inclusion; the PostgreSQL standby is the safety net. The backup cookbook itself is still used by other services. --- roles/mastodon.rb | 1 - site-cookbooks/kosmos-mastodon/metadata.rb | 1 - .../kosmos-mastodon/recipes/backup.rb | 17 ----------------- 3 files changed, 19 deletions(-) delete mode 100644 site-cookbooks/kosmos-mastodon/recipes/backup.rb diff --git a/roles/mastodon.rb b/roles/mastodon.rb index 4b202c7..28761b2 100644 --- a/roles/mastodon.rb +++ b/roles/mastodon.rb @@ -8,5 +8,4 @@ run_list %w( role[postgresql_client] kosmos-mastodon::libretranslate kosmos-mastodon - kosmos-mastodon::backup ) diff --git a/site-cookbooks/kosmos-mastodon/metadata.rb b/site-cookbooks/kosmos-mastodon/metadata.rb index 4e9883f..74dccb4 100644 --- a/site-cookbooks/kosmos-mastodon/metadata.rb +++ b/site-cookbooks/kosmos-mastodon/metadata.rb @@ -6,7 +6,6 @@ description 'Installs/Configures Mastodon' long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) version '0.2.1' -depends 'backup' depends 'elasticsearch' depends 'firewall' depends 'postgresql' 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 From fb0c7b2b1cb744d180e1a9f6dbd460ca810998df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 18:08:22 +0200 Subject: [PATCH 10/21] Add a build phase, make deployment revision-driven Split the deployment into kosmos-mastodon::build (checkout + bundle/yarn/assets, no database) and kosmos-mastodon::deploy (migrations + services), dispatched by the new 'build' attribute. Both phases are idempotent per checked-out revision via stamps, and the migration/restart/search-index chain is now triggered by the deployed revision instead of the git resource, so it still runs when the build was pre-staged. --- roles/mastodon.rb | 3 +- .../kosmos-mastodon/attributes/default.rb | 5 + .../kosmos-mastodon/recipes/build.rb | 141 ++++++++ .../kosmos-mastodon/recipes/default.rb | 325 +----------------- .../kosmos-mastodon/recipes/deploy.rb | 240 +++++++++++++ 5 files changed, 392 insertions(+), 322 deletions(-) create mode 100644 site-cookbooks/kosmos-mastodon/recipes/build.rb create mode 100644 site-cookbooks/kosmos-mastodon/recipes/deploy.rb diff --git a/roles/mastodon.rb b/roles/mastodon.rb index 28761b2..d5f07a4 100644 --- a/roles/mastodon.rb +++ b/roles/mastodon.rb @@ -1,7 +1,8 @@ name "mastodon" default_attributes 'kosmos-mastodon' => { - 'deploy' => false + 'deploy' => false, + 'build' => false } run_list %w( diff --git a/site-cookbooks/kosmos-mastodon/attributes/default.rb b/site-cookbooks/kosmos-mastodon/attributes/default.rb index 237c46e..5b18556 100644 --- a/site-cookbooks/kosmos-mastodon/attributes/default.rb +++ b/site-cookbooks/kosmos-mastodon/attributes/default.rb @@ -12,6 +12,11 @@ node.default["kosmos-mastodon"]["sidekiq_threads"] = 25 # 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" diff --git a/site-cookbooks/kosmos-mastodon/recipes/build.rb b/site-cookbooks/kosmos-mastodon/recipes/build.rb new file mode 100644 index 0000000..95b6b99 --- /dev/null +++ b/site-cookbooks/kosmos-mastodon/recipes/build.rb @@ -0,0 +1,141 @@ +# +# 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" + +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_path, + "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)\"" + +git mastodon_path do + user mastodon_user + group mastodon_user + + repository node["kosmos-mastodon"]["repo"] + revision node["kosmos-mastodon"]["revision"] +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: 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 install --without development,test --deployment" + 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 :create, "file[#{mastodon_path}/tmp/built-revision]", :immediately +end + +file "#{mastodon_path}/tmp/built-revision" do + content lazy { `git -C #{mastodon_path} rev-parse HEAD`.strip } + owner mastodon_user + group mastodon_user + mode "0644" + action :nothing +end diff --git a/site-cookbooks/kosmos-mastodon/recipes/default.rb b/site-cookbooks/kosmos-mastodon/recipes/default.rb index c96e242..9401dd8 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/default.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/default.rb @@ -5,325 +5,8 @@ include_recipe "kosmos-mastodon::dependencies" -# The rest is the actual Mastodon deployment. Skip it when only the runtime -# dependencies should be installed (e.g. to pre-stage a new VM). -return unless node["kosmos-mastodon"]["deploy"] +# Check out and build the application without touching the database. +include_recipe "kosmos-mastodon::build" if node["kosmos-mastodon"]["build"] || node["kosmos-mastodon"]["deploy"] -require 'uri' - -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 - -ruby_version = node["kosmos-mastodon"]["ruby_version"] -ruby_path = "/opt/ruby_build/builds/#{ruby_version}" -bundle_path = "#{ruby_path}/bin/bundle" - -# 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']}" - -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, - "COREPACK_ENABLE_DOWNLOAD_PROMPT" => "0" -} - -git mastodon_path do - user mastodon_user - group mastodon_user - - repository node["kosmos-mastodon"]["repo"] - revision node["kosmos-mastodon"]["revision"] - # Restart services (and run post-deployment migrations) on deployments - notifies :run, "execute[restart mastodon services]", :delayed -end - -execute "restart mastodon services" do - command "systemctl restart mastodon-web mastodon-sidekiq mastodon-sidekiq-scheduler mastodon-streaming" - action :nothing - notifies :run, "execute[rake db:migrate (post-deployment)]", :immediately -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: 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 -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 install --without development,test --deployment" -end - -execute "yarn install" do - environment deploy_env - user mastodon_user - cwd mastodon_path - command "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 - -# 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 -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 -end - -execute "start mastodon search deploy" do - command "systemctl start --no-block mastodon-search-deploy.service" - 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 - -# -# Populate the Elasticsearch indices in the background. The indices and -# mappings are created synchronously by the recipe above; this unit only does -# the (potentially very long) import, so it is started without blocking. -# - -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 - -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/deploy.rb b/site-cookbooks/kosmos-mastodon/recipes/deploy.rb new file mode 100644 index 0000000..b0d4fed --- /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" => mastodon_path, + "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 + +# 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 :create, "file[#{mastodon_path}/tmp/deployed-revision]", :immediately +end + +execute "start mastodon search deploy" do + command "systemctl start --no-block mastodon-search-deploy.service" + action :nothing +end + +file "#{mastodon_path}/tmp/deployed-revision" do + content lazy { `git -C #{mastodon_path} rev-parse HEAD`.strip } + owner mastodon_user + group mastodon_user + mode "0644" + 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 + +# +# Populate the Elasticsearch indices in the background. The indices and +# mappings are created synchronously by the recipe above; this unit only does +# the (potentially very long) import, so it is started without blocking. +# + +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 + +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 From d05f00dae217d6b6a445c6a7eb7a334cd5c786a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 18:08:30 +0200 Subject: [PATCH 11/21] Pre-stage the Mastodon 4.7 build on mastodon-4 --- nodes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes b/nodes index d730967..c313373 160000 --- a/nodes +++ b/nodes @@ -1 +1 @@ -Subproject commit d730967916674ffde7d1e00da792ecda0cdf0b84 +Subproject commit c3133736481d40a14fd5510f2de21b1396dd0f78 From abfd654ae57d8f87203dc92973935837bd48387a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 19:41:43 +0200 Subject: [PATCH 12/21] Clone the private Mastodon repo with an SSH deploy key The repository is private, so use git@gitea.kosmos.org with a read-only deploy key stored in credentials/mastodon (repo_deploy_key). The pinned gitea host key is an attribute. --- .../kosmos-mastodon/attributes/default.rb | 8 +++- .../kosmos-mastodon/recipes/build.rb | 42 ++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/site-cookbooks/kosmos-mastodon/attributes/default.rb b/site-cookbooks/kosmos-mastodon/attributes/default.rb index 5b18556..31f88e3 100644 --- a/site-cookbooks/kosmos-mastodon/attributes/default.rb +++ b/site-cookbooks/kosmos-mastodon/attributes/default.rb @@ -1,4 +1,4 @@ -node.default["kosmos-mastodon"]["repo"] = "https://gitea.kosmos.org/kosmos/mastodon.git" +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" @@ -22,6 +22,12 @@ 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 diff --git a/site-cookbooks/kosmos-mastodon/recipes/build.rb b/site-cookbooks/kosmos-mastodon/recipes/build.rb index 95b6b99..3c85ccd 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/build.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/build.rb @@ -42,16 +42,54 @@ deploy_env = { 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. +directory "#{mastodon_path}/.ssh" do + owner mastodon_user + group mastodon_user + mode "0700" +end + +file "#{mastodon_path}/.ssh/id_ed25519" do + content credentials["repo_deploy_key"] + owner mastodon_user + group mastodon_user + mode "0600" + sensitive true +end + +file "#{mastodon_path}/.ssh/known_hosts" do + content "#{node['kosmos-mastodon']['gitea_ssh_host_key']}\n" + owner mastodon_user + group mastodon_user + mode "0644" +end + +file "#{mastodon_path}/.ssh/config" do + content <<-EOF +Host gitea.kosmos.org + IdentityFile #{mastodon_path}/.ssh/id_ed25519 + IdentitiesOnly yes + StrictHostKeyChecking yes + UserKnownHostsFile #{mastodon_path}/.ssh/known_hosts + EOF + owner mastodon_user + group mastodon_user + mode "0600" +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_path}/.ssh/id_ed25519 -o IdentitiesOnly=yes " \ + "-o StrictHostKeyChecking=yes -o UserKnownHostsFile=#{mastodon_path}/.ssh/known_hosts" end -credentials = data_bag_item('credentials', 'mastodon') - ldap_config = { host: "ldap.kosmos.local", port: 389, From a3877e620eecff811daf43b34326d182e19d4470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 7 Oct 2026 19:49:52 +0200 Subject: [PATCH 13/21] Create the mastodon home directory in the dependencies recipe The user resource does not manage the home directory, so removing /opt/mastodon broke the build phase when it tried to create /opt/mastodon/.ssh. --- site-cookbooks/kosmos-mastodon/recipes/dependencies.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb index f0ff8a0..22ff78e 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb @@ -62,6 +62,12 @@ user mastodon_user do home mastodon_path 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 From aeeb900f146ee46e83759c31ac017fb985f22874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 8 Oct 2026 10:53:51 +0200 Subject: [PATCH 14/21] Add repo deploy key --- data_bags/credentials/mastodon.json | 103 +++++++++++++++------------- 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/data_bags/credentials/mastodon.json b/data_bags/credentials/mastodon.json index b3444b3..945cc63 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": "dLzqfmMWOTjxuFG4mpDE2NZQXrox0LtB4vImJ+OnJPyhMCzAVyJLLkASlW2I\n1K5vpU0D\n", + "iv": "UjP8/i2nWyB6Y8PG\n", + "auth_tag": "5CXecOmQ+cvoFL+6Whsmdg==\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": "bXhhg6u9lDoR6cFES9OFKgT2D9S5+5A7Ih3vZU6dE90uqpjlC5LKi3/C5G+2\nioOid9yX\n", + "iv": "Tr+2iXpGsxJgvitN\n", + "auth_tag": "LEH6C7uMQylUkuHI8HjjMg==\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": "msXy9APducZeMrhdPGU5nD6llArdgj9z73EPQGVHJqfjyip78+FNqWBncYO7\nlaXg+80K\n", + "iv": "ELjdaWI3+mG8JqwB\n", + "auth_tag": "JAa+X0tvy0QEOHlMzmKdFQ==\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": "g/rFNYcqA/gpUdT2Lnha0gg6KxCxo4vwldYfQhrlM4589we2+im4DBKd50Tr\nwa94vb3CHxLc1tOGIVSOgMBU/iKStqTuVQN0S5vhA1a0mzBSJAeE3ov4wthK\nQC2xCvL2jSJalMBbp5Z087Ci8nF0W3BtT6Uiwm4B+RO/nxlkJsXZatcgIn0z\n+4SlRLdtFt9uZHym\n", + "iv": "PRr+hDdX/iMsjsxf\n", + "auth_tag": "EfjSgWj3YDPJcZhGjk72LA==\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": "5+IfyloBiSBFw5/KXLmaqDzOITvFvfhFdlhUsPlVTDo4f3Qc3NH2Ftp+/GLt\nBPxpId+p68COD82lJ0fO1GN/d+ifOJvB3DsR/frY6OxceGSQ15bAvU/77r1v\n/Bgb9I4u6XRXLGazC9smKQgrFNxD5L1o1L5s5AYYqk/qIw91YBF/WlWlN88+\nQNkwXu0hHxFZG1jg\n", + "iv": "P3y2itXucxT44jlB\n", + "auth_tag": "CZNqKaE7SgEoXvn8hzJS7g==\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": "+Xw/ctktuap3h5dvYJS9c5fSde9UuKW7uruixo4z28LEEWwfrBK6h0rj9JAU\ndb2/dDUvKwrQu3Xm9bvwJ5qMnvv0zxYjbUSDig0r6g1kMVJFT7HWn5egibzQ\nyb63XRESgVRmyo8y1/raTUNRxJS6HwtZIAgQ7wBF0Vy5q37zeG9jlUwENjl2\nNzLFiKpAiHLCi/cg\n", + "iv": "kvPBaulHEauvKhZq\n", + "auth_tag": "QNB86E77rEqX/Y5W8IlS4A==\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": "nwZctwxMLdToJ73ymJamz+OtZoeOe6tnYv4/LgKDpYi57H//OMVO\n", + "iv": "xlR9EqOp0dB/Evi/\n", + "auth_tag": "kf2P6qq1rk1hwQM8ksKITQ==\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": "SJz+MGlN3PK9IkYYV/cW5RtjJMTIXPnUi7ZZ0VMeh7T5kCX/Ox6qUKfSZ9uy\npkFNpt4FTgchpPK5TI0=\n", + "iv": "Wp6RDgUcjGmwblC6\n", + "auth_tag": "E8f8ovebMGbEDIvvefcEnQ==\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": "lFPH/RThUyXJDJGgagev8Tkax1Yj46norLRB1aNaaXWsA/dleeb/IQfa2+aA\nTRVo4OLG1MSAoDZreAGfM2W7DGS60KdGtMWP8h2h4g8=\n", + "iv": "j9/QJzDyN2Oke4sb\n", + "auth_tag": "DTXpktf2qPVk6F8i49IVJA==\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": "reL9fL0cW1UatAZ9GOw+fZ3I5WZKoB2TetA2GVBqUQ==\n", + "iv": "aemLAAw+lsTeVS7a\n", + "auth_tag": "81fh8IRzPHZven7cTmg4oQ==\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": "zMsZYZswxzkOzLO8LvHGNp0J9rDsZlPGIFVVZyXgJR+VcRMyAWfr8j/ge0u0\neA==\n", + "iv": "ZNTOkvFKyXuLE8PN\n", + "auth_tag": "zLd/wXouzHZPNs1ouRJ7YQ==\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": "hZ3mrD+sUaEi3GuZst3qBVkgA7m+h+1E+Tc3QcN7Kc7zNgmm25qNhuaPuvni\nVuVN1aCb\n", + "iv": "MYqQ6KJrVOML92Dz\n", + "auth_tag": "1YWbssQB9sy8Y8DdRyn2dA==\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": "vZjelfTy8kjQNgb4rcu63c0AGtQSUNMQQwA4rPDA7VZBJmnclrTneT00AOUa\nFGrRdRog6nOBnyt/Q2ZdBYS7\n", + "iv": "4NDFBu13w/iwGsD9\n", + "auth_tag": "q6oiMgtTUJG4FG5MRNoRMg==\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": "fQhlpXkyFu1XnP2DSJeZrHPkCnwX48GOlVgnkEMP0U9lmX3bm6MvKCd/n4XD\nBJBN45cBfE8jjiOFwjcHDaX57RJACjF9duq0B4MO3nBdF/1Q2MCzx2wuoVzG\n3mbf9trX2BYiCHw4qPZnCd8=\n", + "iv": "t+YkDSoj7aTaabMT\n", + "auth_tag": "fXvBmQDdFSWC3VXRaQHGKQ==\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": "fFgRbumLg0jJN47kw5ysK28XFuoqyhD0x9UeKqDX6bNO6fRmXM4ZRESvnlUT\n", + "iv": "5v8aJQFUx1RUoaAf\n", + "auth_tag": "QqFsJrlBHF+NZpFC1zlbIw==\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": "1uFgW5ayO9qhmZyAavt+Th59GUY7+oyMgnJyaTYARFww/srB7xVb0VJKTwRw\nRGDy1EmQ5ZTbD/M/Hf+Zq1mq2qEkuhDa3fYpOXtnqo8w6H/P/J8=\n", + "iv": "CZ3Dndqn0i/1+F7m\n", + "auth_tag": "G2pcS7oaHRtFvywTqEKaFg==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "repo_deploy_key": { + "encrypted_data": "TXYLZ1orX8+lT33Kss5DJ/NPJUGD93yfJfyevzNPA7yYhuT+SdzCTUGLNiRr\nc09fJbvinDVHOjq4P/tCVmVuQs9wW9enC70apvyk0gqi9t81GU40h3muqVY/\nJOzWR0HR+fW55vJvSWTpH4R+Uv6TWfTdQ2+9U/zO+Q==\n", + "iv": "o4sCEGnCZvz1xxfD\n", + "auth_tag": "zbRk0sQLAmSbwqzg7F32dw==\n", "version": 3, "cipher": "aes-256-gcm" } From b0718a5dac73b04d67e8e646bd13d51334ad3ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 8 Oct 2026 11:30:09 +0200 Subject: [PATCH 15/21] Ensure the SSH deploy key ends with a newline OpenSSH's PEM parser rejects a private key file without a trailing newline ('error in libcrypto'). --- site-cookbooks/kosmos-mastodon/recipes/build.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site-cookbooks/kosmos-mastodon/recipes/build.rb b/site-cookbooks/kosmos-mastodon/recipes/build.rb index 3c85ccd..a849bd8 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/build.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/build.rb @@ -52,7 +52,8 @@ directory "#{mastodon_path}/.ssh" do end file "#{mastodon_path}/.ssh/id_ed25519" do - content credentials["repo_deploy_key"] + # 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" From 0696d0337479614ea3175502004e6793beff21c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 8 Oct 2026 11:34:46 +0200 Subject: [PATCH 16/21] Replace the removed bundler --deployment flag Bundler 4 (shipped with Ruby 4) removed --deployment. Use 'bundle config set --local deployment true' (frozen + vendor/bundle) and the 'without' setting instead, in the Mastodon, akkounts and liquor-cabinet cookbooks. --- site-cookbooks/kosmos-akkounts/recipes/default.rb | 4 +++- site-cookbooks/kosmos-mastodon/recipes/build.rb | 4 +++- site-cookbooks/liquor_cabinet/recipes/default.rb | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) 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/recipes/build.rb b/site-cookbooks/kosmos-mastodon/recipes/build.rb index a849bd8..2ed880f 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/build.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/build.rb @@ -149,7 +149,9 @@ 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 install --without development,test --deployment" + command "bundle config set --local deployment true && " \ + "bundle config set --local without 'development test' && " \ + "bundle install" not_if build_uptodate end 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 From 1b564c285d7b6f6381ffed929bae7320c435cc49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 8 Oct 2026 11:43:47 +0200 Subject: [PATCH 17/21] Give the mastodon user a /home/mastodon home The mastodon user's home was /opt/mastodon, i.e. the clone destination, so writing the SSH deploy key into ~/.ssh made the directory non-empty and Chef's git resource silently skipped cloning (it only clones into an empty directory). Use a dedicated /home/mastodon home for the user and keep the clone destination separate. Also clear a non-git (partial) clone destination before cloning. --- .../kosmos-mastodon/recipes/build.rb | 29 ++++++++++++------- .../kosmos-mastodon/recipes/dependencies.rb | 9 +++++- .../kosmos-mastodon/recipes/deploy.rb | 2 +- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/site-cookbooks/kosmos-mastodon/recipes/build.rb b/site-cookbooks/kosmos-mastodon/recipes/build.rb index 2ed880f..ea369e0 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/build.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/build.rb @@ -13,6 +13,7 @@ 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}" @@ -32,7 +33,7 @@ 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, + "HOME" => mastodon_home, "RAILS_ENV" => rails_env, "NODE_ENV" => rails_env, "COREPACK_ENABLE_DOWNLOAD_PROMPT" => "0" @@ -44,14 +45,15 @@ build_uptodate = "test -f #{mastodon_path}/tmp/built-revision && " \ credentials = data_bag_item('credentials', 'mastodon') -# The repository is private; clone it with a read-only SSH deploy key. -directory "#{mastodon_path}/.ssh" do +# 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_path}/.ssh/id_ed25519" do +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 @@ -60,26 +62,33 @@ file "#{mastodon_path}/.ssh/id_ed25519" do sensitive true end -file "#{mastodon_path}/.ssh/known_hosts" do +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_path}/.ssh/config" do +file "#{mastodon_home}/.ssh/config" do content <<-EOF Host gitea.kosmos.org - IdentityFile #{mastodon_path}/.ssh/id_ed25519 + IdentityFile #{mastodon_home}/.ssh/id_ed25519 IdentitiesOnly yes StrictHostKeyChecking yes - UserKnownHostsFile #{mastodon_path}/.ssh/known_hosts + 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 @@ -87,8 +96,8 @@ git mastodon_path do repository node["kosmos-mastodon"]["repo"] revision node["kosmos-mastodon"]["revision"] environment "GIT_SSH_COMMAND" => - "ssh -i #{mastodon_path}/.ssh/id_ed25519 -o IdentitiesOnly=yes " \ - "-o StrictHostKeyChecking=yes -o UserKnownHostsFile=#{mastodon_path}/.ssh/known_hosts" + "ssh -i #{mastodon_home}/.ssh/id_ed25519 -o IdentitiesOnly=yes " \ + "-o StrictHostKeyChecking=yes -o UserKnownHostsFile=#{mastodon_home}/.ssh/known_hosts" end ldap_config = { diff --git a/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb index 22ff78e..a71f1cd 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/dependencies.rb @@ -49,6 +49,7 @@ elasticsearch_service 'elasticsearch' mastodon_path = node["kosmos-mastodon"]["directory"] mastodon_user = "mastodon" +mastodon_home = "/home/#{mastodon_user}" group mastodon_user do gid 62786 @@ -59,7 +60,13 @@ user mastodon_user do uid 62786 gid 62786 shell "/bin/bash" - home mastodon_path + home mastodon_home +end + +directory mastodon_home do + owner mastodon_user + group mastodon_user + mode "0755" end directory mastodon_path do diff --git a/site-cookbooks/kosmos-mastodon/recipes/deploy.rb b/site-cookbooks/kosmos-mastodon/recipes/deploy.rb index b0d4fed..8346257 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/deploy.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/deploy.rb @@ -24,7 +24,7 @@ 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, + "HOME" => "/home/#{mastodon_user}", "RAILS_ENV" => rails_env, "NODE_ENV" => rails_env, "COREPACK_ENABLE_DOWNLOAD_PROMPT" => "0" From af4983d7478b4a8014d378558f5bd67ba90204bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 8 Oct 2026 11:51:23 +0200 Subject: [PATCH 18/21] Record revision stamps as the mastodon user The stamp files' lazy git call ran as root, which git rejects on the mastodon-owned repository (dubious ownership), silently writing an empty stamp. Use execute resources with the mastodon user instead so git works and the revision is recorded. --- site-cookbooks/kosmos-mastodon/recipes/build.rb | 16 +++++++++------- site-cookbooks/kosmos-mastodon/recipes/deploy.rb | 16 +++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/site-cookbooks/kosmos-mastodon/recipes/build.rb b/site-cookbooks/kosmos-mastodon/recipes/build.rb index ea369e0..839802f 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/build.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/build.rb @@ -179,13 +179,15 @@ execute "rake assets:precompile" do cwd mastodon_path command "bundle exec rake assets:precompile" not_if build_uptodate - notifies :create, "file[#{mastodon_path}/tmp/built-revision]", :immediately + notifies :run, "execute[record built revision]", :immediately end -file "#{mastodon_path}/tmp/built-revision" do - content lazy { `git -C #{mastodon_path} rev-parse HEAD`.strip } - owner mastodon_user - group mastodon_user - mode "0644" - action :nothing +# 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/deploy.rb b/site-cookbooks/kosmos-mastodon/recipes/deploy.rb index 8346257..aebac8f 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/deploy.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/deploy.rb @@ -131,7 +131,7 @@ execute "tootctl search deploy (create indices)" do timeout 3_600 action :nothing notifies :run, "execute[start mastodon search deploy]", :immediately - notifies :create, "file[#{mastodon_path}/tmp/deployed-revision]", :immediately + notifies :run, "execute[record deployed revision]", :immediately end execute "start mastodon search deploy" do @@ -139,12 +139,14 @@ execute "start mastodon search deploy" do action :nothing end -file "#{mastodon_path}/tmp/deployed-revision" do - content lazy { `git -C #{mastodon_path} rev-parse HEAD`.strip } - owner mastodon_user - group mastodon_user - mode "0644" - action :nothing +# 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 From 8ac0b965b270e1f34df2b3d3b5410a0cac5c9b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 8 Oct 2026 12:21:24 +0200 Subject: [PATCH 19/21] Declare the search deploy unit before the migration chain The migration chain starts mastodon-search-deploy.service immediately, but the systemd_unit resource was declared later in the recipe, so the unit did not exist yet and systemctl failed with 'Unit not found'. --- .../kosmos-mastodon/recipes/deploy.rb | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/site-cookbooks/kosmos-mastodon/recipes/deploy.rb b/site-cookbooks/kosmos-mastodon/recipes/deploy.rb index aebac8f..9574eea 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/deploy.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/deploy.rb @@ -94,6 +94,28 @@ execute "restart mastodon services" do 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 @@ -203,30 +225,6 @@ systemd_unit 'mastodon-delete-old-media-cache.timer' do action [:create, :enable, :start] end -# -# Populate the Elasticsearch indices in the background. The indices and -# mappings are created synchronously by the recipe above; this unit only does -# the (potentially very long) import, so it is started without blocking. -# - -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 - firewall_rule "mastodon_app" do port node['kosmos-mastodon']['app_port'] source "10.1.1.0/24" From 2bb5b582ec04c971e94309e9cf6d60f7591c7839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 8 Oct 2026 12:43:35 +0200 Subject: [PATCH 20/21] Deploy Mastodon from now on The mastodon role now always deploys (deploy=true), so a plain converge rebuilds and restarts on new production-4.7 commits. --- roles/mastodon.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/roles/mastodon.rb b/roles/mastodon.rb index d5f07a4..42ba22a 100644 --- a/roles/mastodon.rb +++ b/roles/mastodon.rb @@ -1,8 +1,7 @@ name "mastodon" default_attributes 'kosmos-mastodon' => { - 'deploy' => false, - 'build' => false + 'deploy' => true } run_list %w( From 63e44017155271278f1b84f042ec11110b9baa4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 8 Oct 2026 14:28:16 +0200 Subject: [PATCH 21/21] Update Mastodon data bag, nodes --- data_bags/credentials/mastodon.json | 102 ++++++++++++++-------------- nodes | 2 +- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/data_bags/credentials/mastodon.json b/data_bags/credentials/mastodon.json index 945cc63..bfdfa13 100644 --- a/data_bags/credentials/mastodon.json +++ b/data_bags/credentials/mastodon.json @@ -1,121 +1,121 @@ { "id": "mastodon", "active_record_encryption_deterministic_key": { - "encrypted_data": "dLzqfmMWOTjxuFG4mpDE2NZQXrox0LtB4vImJ+OnJPyhMCzAVyJLLkASlW2I\n1K5vpU0D\n", - "iv": "UjP8/i2nWyB6Y8PG\n", - "auth_tag": "5CXecOmQ+cvoFL+6Whsmdg==\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": "bXhhg6u9lDoR6cFES9OFKgT2D9S5+5A7Ih3vZU6dE90uqpjlC5LKi3/C5G+2\nioOid9yX\n", - "iv": "Tr+2iXpGsxJgvitN\n", - "auth_tag": "LEH6C7uMQylUkuHI8HjjMg==\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": "msXy9APducZeMrhdPGU5nD6llArdgj9z73EPQGVHJqfjyip78+FNqWBncYO7\nlaXg+80K\n", - "iv": "ELjdaWI3+mG8JqwB\n", - "auth_tag": "JAa+X0tvy0QEOHlMzmKdFQ==\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": "g/rFNYcqA/gpUdT2Lnha0gg6KxCxo4vwldYfQhrlM4589we2+im4DBKd50Tr\nwa94vb3CHxLc1tOGIVSOgMBU/iKStqTuVQN0S5vhA1a0mzBSJAeE3ov4wthK\nQC2xCvL2jSJalMBbp5Z087Ci8nF0W3BtT6Uiwm4B+RO/nxlkJsXZatcgIn0z\n+4SlRLdtFt9uZHym\n", - "iv": "PRr+hDdX/iMsjsxf\n", - "auth_tag": "EfjSgWj3YDPJcZhGjk72LA==\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": "5+IfyloBiSBFw5/KXLmaqDzOITvFvfhFdlhUsPlVTDo4f3Qc3NH2Ftp+/GLt\nBPxpId+p68COD82lJ0fO1GN/d+ifOJvB3DsR/frY6OxceGSQ15bAvU/77r1v\n/Bgb9I4u6XRXLGazC9smKQgrFNxD5L1o1L5s5AYYqk/qIw91YBF/WlWlN88+\nQNkwXu0hHxFZG1jg\n", - "iv": "P3y2itXucxT44jlB\n", - "auth_tag": "CZNqKaE7SgEoXvn8hzJS7g==\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": "+Xw/ctktuap3h5dvYJS9c5fSde9UuKW7uruixo4z28LEEWwfrBK6h0rj9JAU\ndb2/dDUvKwrQu3Xm9bvwJ5qMnvv0zxYjbUSDig0r6g1kMVJFT7HWn5egibzQ\nyb63XRESgVRmyo8y1/raTUNRxJS6HwtZIAgQ7wBF0Vy5q37zeG9jlUwENjl2\nNzLFiKpAiHLCi/cg\n", - "iv": "kvPBaulHEauvKhZq\n", - "auth_tag": "QNB86E77rEqX/Y5W8IlS4A==\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": "nwZctwxMLdToJ73ymJamz+OtZoeOe6tnYv4/LgKDpYi57H//OMVO\n", - "iv": "xlR9EqOp0dB/Evi/\n", - "auth_tag": "kf2P6qq1rk1hwQM8ksKITQ==\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": "SJz+MGlN3PK9IkYYV/cW5RtjJMTIXPnUi7ZZ0VMeh7T5kCX/Ox6qUKfSZ9uy\npkFNpt4FTgchpPK5TI0=\n", - "iv": "Wp6RDgUcjGmwblC6\n", - "auth_tag": "E8f8ovebMGbEDIvvefcEnQ==\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": "lFPH/RThUyXJDJGgagev8Tkax1Yj46norLRB1aNaaXWsA/dleeb/IQfa2+aA\nTRVo4OLG1MSAoDZreAGfM2W7DGS60KdGtMWP8h2h4g8=\n", - "iv": "j9/QJzDyN2Oke4sb\n", - "auth_tag": "DTXpktf2qPVk6F8i49IVJA==\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": "reL9fL0cW1UatAZ9GOw+fZ3I5WZKoB2TetA2GVBqUQ==\n", - "iv": "aemLAAw+lsTeVS7a\n", - "auth_tag": "81fh8IRzPHZven7cTmg4oQ==\n", + "encrypted_data": "2V7nesfImnY6DooFctBupXKyexLqTUUoY5M7wQE0FA==\n", + "iv": "wjRF6W7JkUUS6Qn8\n", + "auth_tag": "PhuKjWIla3yFCprFgzxilA==\n", "version": 3, "cipher": "aes-256-gcm" }, "smtp_user_name": { - "encrypted_data": "zMsZYZswxzkOzLO8LvHGNp0J9rDsZlPGIFVVZyXgJR+VcRMyAWfr8j/ge0u0\neA==\n", - "iv": "ZNTOkvFKyXuLE8PN\n", - "auth_tag": "zLd/wXouzHZPNs1ouRJ7YQ==\n", + "encrypted_data": "rQR5ut5VCbQf3dLz6els3BSU3Lj1dZp0k6EaEZnM2E41HrQbMCAgMWrepTaO\nCQ==\n", + "iv": "5e/KzhAz6ALZzxOm\n", + "auth_tag": "wBJLKVHyB1JKeZ9hS8uUIQ==\n", "version": 3, "cipher": "aes-256-gcm" }, "smtp_password": { - "encrypted_data": "hZ3mrD+sUaEi3GuZst3qBVkgA7m+h+1E+Tc3QcN7Kc7zNgmm25qNhuaPuvni\nVuVN1aCb\n", - "iv": "MYqQ6KJrVOML92Dz\n", - "auth_tag": "1YWbssQB9sy8Y8DdRyn2dA==\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": "vZjelfTy8kjQNgb4rcu63c0AGtQSUNMQQwA4rPDA7VZBJmnclrTneT00AOUa\nFGrRdRog6nOBnyt/Q2ZdBYS7\n", - "iv": "4NDFBu13w/iwGsD9\n", - "auth_tag": "q6oiMgtTUJG4FG5MRNoRMg==\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": "fQhlpXkyFu1XnP2DSJeZrHPkCnwX48GOlVgnkEMP0U9lmX3bm6MvKCd/n4XD\nBJBN45cBfE8jjiOFwjcHDaX57RJACjF9duq0B4MO3nBdF/1Q2MCzx2wuoVzG\n3mbf9trX2BYiCHw4qPZnCd8=\n", - "iv": "t+YkDSoj7aTaabMT\n", - "auth_tag": "fXvBmQDdFSWC3VXRaQHGKQ==\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": "fFgRbumLg0jJN47kw5ysK28XFuoqyhD0x9UeKqDX6bNO6fRmXM4ZRESvnlUT\n", - "iv": "5v8aJQFUx1RUoaAf\n", - "auth_tag": "QqFsJrlBHF+NZpFC1zlbIw==\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": "1uFgW5ayO9qhmZyAavt+Th59GUY7+oyMgnJyaTYARFww/srB7xVb0VJKTwRw\nRGDy1EmQ5ZTbD/M/Hf+Zq1mq2qEkuhDa3fYpOXtnqo8w6H/P/J8=\n", - "iv": "CZ3Dndqn0i/1+F7m\n", - "auth_tag": "G2pcS7oaHRtFvywTqEKaFg==\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": "TXYLZ1orX8+lT33Kss5DJ/NPJUGD93yfJfyevzNPA7yYhuT+SdzCTUGLNiRr\nc09fJbvinDVHOjq4P/tCVmVuQs9wW9enC70apvyk0gqi9t81GU40h3muqVY/\nJOzWR0HR+fW55vJvSWTpH4R+Uv6TWfTdQ2+9U/zO+Q==\n", - "iv": "o4sCEGnCZvz1xxfD\n", - "auth_tag": "zbRk0sQLAmSbwqzg7F32dw==\n", + "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 c313373..58e8fb8 160000 --- a/nodes +++ b/nodes @@ -1 +1 @@ -Subproject commit c3133736481d40a14fd5510f2de21b1396dd0f78 +Subproject commit 58e8fb83fd96299723bfd8ca63b393eede8fd67a