From 1c96ef42c6a791ad61f20655eaaa4fa9ea57ee8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sat, 24 Oct 2026 15:22:32 +0200 Subject: [PATCH 01/18] Use Cinc by default for knife-zero bootstraps --- .chef/config.rb.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.chef/config.rb.example b/.chef/config.rb.example index 4663d82..3a28aee 100644 --- a/.chef/config.rb.example +++ b/.chef/config.rb.example @@ -12,3 +12,7 @@ local_mode true # Chef local mode, replacing Solo # https://docs.chef.io/attribute_persistence/ # Source of truth: site-cookbooks/kosmos-base/files/default/chef_attributes.rb client_d_dir(File.expand_path("../site-cookbooks/kosmos-base/files/default/chef_attributes.rb")) + +# Chef binaries on packages.chef.io are license-gated (HTTP 402); use the +# community Cinc build for bootstraps and converged nodes. +knife[:alter_project] = "cinc" From 6a9ff1eea0618daf2d175f65b61476f9ef11e268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sat, 24 Oct 2026 15:29:07 +0200 Subject: [PATCH 02/18] Support Cinc in kosmos-base Resolve the client config dir at runtime (Chef::Config[:config_file] / :client_d_dir) instead of hardcoding /etc/chef, and create client.d when missing so fresh nodes work. Reload only the attributes file we just wrote, not the whole client.rb: reloading client.rb reset chef_server_url and dropped knife-zero's -S (TCP) override, so later server calls fell back to socketless chefzero and failed with No socketless Cinc Zero server. --- site-cookbooks/kosmos-base/recipes/default.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/site-cookbooks/kosmos-base/recipes/default.rb b/site-cookbooks/kosmos-base/recipes/default.rb index 40bbac8..21a44cb 100644 --- a/site-cookbooks/kosmos-base/recipes/default.rb +++ b/site-cookbooks/kosmos-base/recipes/default.rb @@ -3,9 +3,19 @@ # Recipe:: default # +# Resolve the client's own config paths so this works for both Chef (/etc/chef) +# and Cinc (/etc/cinc) rather than hardcoding /etc/chef. +config_file = Chef::Config[:config_file] +client_d_dir = Chef::Config[:client_d_dir] + +directory client_d_dir do + recursive true + mode "0755" +end + ruby_block "cleanup client.rb" do block do - fe = Chef::Util::FileEdit.new("/etc/chef/client.rb") + fe = Chef::Util::FileEdit.new(config_file) # Delete whitelist attribute definitions, everything is defined in attributes.rb fe.search_file_delete_line(/^.*_attribute_whitelist.*/) fe.search_file_delete_line(/^allowed_.*_attributes.*/) @@ -13,7 +23,7 @@ ruby_block "cleanup client.rb" do end end -cookbook_file "/etc/chef/client.d/attributes.rb" do +cookbook_file "#{client_d_dir}/attributes.rb" do source "chef_attributes.rb" mode "0644" notifies :create, "ruby_block[reload_client_config]", :immediately @@ -21,7 +31,10 @@ end ruby_block "reload_client_config" do block do - Chef::Config.from_file("/etc/chef/client.rb") + # Reload only the attribute-persistence config we just wrote. Reloading the + # whole client.rb would reset chef_server_url and break knife-zero's -S + # (TCP) override, making server calls fall back to socketless chefzero. + Chef::Config.from_file("#{client_d_dir}/attributes.rb") end action :nothing end From c76921da78892e981a81df2eb755642ebaa66abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sat, 24 Oct 2026 15:29:13 +0200 Subject: [PATCH 03/18] Vendor CAcert roots instead of fetching from cacert.org cacert.org is intermittently unreachable, which fails bootstraps at the remote_file step. Ship both roots in the cookbook and install them with cookbook_file; contents match what was already installed fleet-wide. --- .../files/default/cacert/class3.crt | 42 +++++++++++++++++++ .../kosmos-base/files/default/cacert/root.crt | 41 ++++++++++++++++++ site-cookbooks/kosmos-base/recipes/default.rb | 9 ++-- 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 site-cookbooks/kosmos-base/files/default/cacert/class3.crt create mode 100644 site-cookbooks/kosmos-base/files/default/cacert/root.crt diff --git a/site-cookbooks/kosmos-base/files/default/cacert/class3.crt b/site-cookbooks/kosmos-base/files/default/cacert/class3.crt new file mode 100644 index 0000000..087ca0e --- /dev/null +++ b/site-cookbooks/kosmos-base/files/default/cacert/class3.crt @@ -0,0 +1,42 @@ +-----BEGIN CERTIFICATE----- +MIIHWTCCBUGgAwIBAgIDCkGKMA0GCSqGSIb3DQEBCwUAMHkxEDAOBgNVBAoTB1Jv +b3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEiMCAGA1UEAxMZ +Q0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYSc3VwcG9y +dEBjYWNlcnQub3JnMB4XDTExMDUyMzE3NDgwMloXDTIxMDUyMDE3NDgwMlowVDEU +MBIGA1UEChMLQ0FjZXJ0IEluYy4xHjAcBgNVBAsTFWh0dHA6Ly93d3cuQ0FjZXJ0 +Lm9yZzEcMBoGA1UEAxMTQ0FjZXJ0IENsYXNzIDMgUm9vdDCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAKtJNRFIfNImflOUz0Op3SjXQiqL84d4GVh8D57a +iX3h++tykA10oZZkq5+gJJlz2uJVdscXe/UErEa4w75/ZI0QbCTzYZzA8pD6Ueb1 +aQFjww9W4kpCz+JEjCUoqMV5CX1GuYrz6fM0KQhF5Byfy5QEHIGoFLOYZcRD7E6C +jQnRvapbjZLQ7N6QxX8KwuPr5jFaXnQ+lzNZ6MMDPWAzv/fRb0fEze5ig1JuLgia +pNkVGJGmhZJHsK5I6223IeyFGmhyNav/8BBdwPSUp2rVO5J+TJAFfpPBLIukjmJ0 +FXFuC3ED6q8VOJrU0gVyb4z5K+taciX5OUbjchs+BMNkJyIQKopPWKcDrb60LhPt +XapI19V91Cp7XPpGBFDkzA5CW4zt2/LP/JaT4NsRNlRiNDiPDGCbO5dWOK3z0luL +oFvqTpa4fNfVoIZwQNORKbeiPK31jLvPGpKK5DR7wNhsX+kKwsOnIJpa3yxdUly6 +R9Wb7yQocDggL9V/KcCyQQNokszgnMyXS0XvOhAKq3A6mJVwrTWx6oUrpByAITGp +rmB6gCZIALgBwJNjVSKRPFbnr9s6JfOPMVTqJouBWfmh0VMRxXudA/Z0EeBtsSw/ +LIaRmXGapneLNGDRFLQsrJ2vjBDTn8Rq+G8T/HNZ92ZCdB6K4/jc0m+YnMtHmJVA +BfvpAgMBAAGjggINMIICCTAdBgNVHQ4EFgQUdahxYEyIE/B42Yl3tW3Fid+8sXow +gaMGA1UdIwSBmzCBmIAUFrUyG9TH8+DmjvO90rA67rI5GNGhfaR7MHkxEDAOBgNV +BAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEiMCAG +A1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYS +c3VwcG9ydEBjYWNlcnQub3JnggEAMA8GA1UdEwEB/wQFMAMBAf8wXQYIKwYBBQUH +AQEEUTBPMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5DQWNlcnQub3JnLzAoBggr +BgEFBQcwAoYcaHR0cDovL3d3dy5DQWNlcnQub3JnL2NhLmNydDBKBgNVHSAEQzBB +MD8GCCsGAQQBgZBKMDMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuQ0FjZXJ0Lm9y +Zy9pbmRleC5waHA/aWQ9MTAwNAYJYIZIAYb4QgEIBCcWJWh0dHA6Ly93d3cuQ0Fj +ZXJ0Lm9yZy9pbmRleC5waHA/aWQ9MTAwUAYJYIZIAYb4QgENBEMWQVRvIGdldCB5 +b3VyIG93biBjZXJ0aWZpY2F0ZSBmb3IgRlJFRSwgZ28gdG8gaHR0cDovL3d3dy5D +QWNlcnQub3JnMA0GCSqGSIb3DQEBCwUAA4ICAQApKIWuRKm5r6R5E/CooyuXYPNc +7uMvwfbiZqARrjY3OnYVBFPqQvX56sAV2KaC2eRhrnILKVyQQ+hBsuF32wITRHhH +Va9Y/MyY9kW50SD42CEH/m2qc9SzxgfpCYXMO/K2viwcJdVxjDm1Luq+GIG6sJO4 +D+Pm1yaMMVpyA4RS5qb1MyJFCsgLDYq4Nm+QCaGrvdfVTi5xotSu+qdUK+s1jVq3 +VIgv7nSf7UgWyg1I0JTTrKSi9iTfkuO960NAkW4cGI5WtIIS86mTn9S8nK2cde5a +lxuV53QtHA+wLJef+6kzOXrnAzqSjiL2jA3k2X4Ndhj3AfnvlpaiVXPAPHG0HRpW +Q7fDCo1y/OIQCQtBzoyUoPkD/XFzS4pXM+WOdH4VAQDmzEoc53+VGS3FpQyLu7Xt +hbNc09+4ufLKxw0BFKxwWMWMjTPUnWajGlCVI/xI4AZDEtnNp4Y5LzZyo4AQ5OHz +0ctbGsDkgJp8E3MGT9ujayQKurMcvEp4u+XjdTilSKeiHq921F73OIZWWonO1sOn +ebJSoMbxhbQljPI/lrMQ2Y1sVzufb4Y6GIIiNsiwkTjbKqGTqoQ/9SdlrnPVyNXT +d+pLncdBu8fA46A/5H2kjXPmEkvfoXNzczqA6NXLji/L6hOn1kGLrPo8idck9U60 +4GGSt/M3mMS+lqO3ig== +-----END CERTIFICATE----- diff --git a/site-cookbooks/kosmos-base/files/default/cacert/root.crt b/site-cookbooks/kosmos-base/files/default/cacert/root.crt new file mode 100644 index 0000000..e7dfc82 --- /dev/null +++ b/site-cookbooks/kosmos-base/files/default/cacert/root.crt @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290 +IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB +IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA +Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO +BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi +MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ +ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ +8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6 +zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y +fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7 +w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc +G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k +epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q +laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ +QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU +fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826 +YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w +ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY +gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe +MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0 +IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy +dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw +czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0 +dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl +aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC +AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg +b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB +ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc +nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg +18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c +gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl +Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY +sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T +SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF +CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum +GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk +zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW +omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD +-----END CERTIFICATE----- diff --git a/site-cookbooks/kosmos-base/recipes/default.rb b/site-cookbooks/kosmos-base/recipes/default.rb index 21a44cb..24cd355 100644 --- a/site-cookbooks/kosmos-base/recipes/default.rb +++ b/site-cookbooks/kosmos-base/recipes/default.rb @@ -90,10 +90,11 @@ unless node.chef_environment == "development" action :create end - ["http://www.cacert.org/certs/root.crt", "http://www.cacert.org/certs/class3.crt"].each do |cert| - remote_file "/usr/local/share/ca-certificates/cacert/#{File.basename(cert)}" do - source cert - action :create_if_missing + # CAcert roots, vendored so bootstraps do not depend on cacert.org being + # reachable at converge time. + %w(root.crt class3.crt).each do |cert| + cookbook_file "/usr/local/share/ca-certificates/cacert/#{cert}" do + source "cacert/#{cert}" notifies :run, "execute[update-ca-certificates]", :immediately end end From 5bb14993718509f0eb24f376276cf86a9242f98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sat, 24 Oct 2026 15:29:21 +0200 Subject: [PATCH 04/18] Move Node services onto patched releases for the eMudhra CA incident Bump Node to patched releases (mastodon/sockethub 24.22.1, RTL 22.23.4). Force the pinned major's latest patch in kosmos-nodejs: nodejs_install only selects the repo major and a bare `package nodejs` never upgrades an already installed package. For hosts stuck on Node 16 (no patched 16.x), run the services with NODE_OPTIONS=--use-openssl-ca so they use the patched system trust store: ipfs-pinner, kredits-github, hubot botka/hal8000, sockethub. --- site-cookbooks/kosmos-bitcoin/recipes/rtl.rb | 2 +- .../kosmos-hubot/recipes/botka_irc-libera-chat.rb | 3 +++ site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb | 3 +++ site-cookbooks/kosmos-ipfs/recipes/kredits_pinner.rb | 5 ++++- site-cookbooks/kosmos-mastodon/attributes/default.rb | 2 +- site-cookbooks/kosmos-nodejs/recipes/default.rb | 8 ++++++++ site-cookbooks/kredits-github/recipes/default.rb | 5 ++++- site-cookbooks/sockethub/recipes/default.rb | 3 ++- 8 files changed, 26 insertions(+), 5 deletions(-) diff --git a/site-cookbooks/kosmos-bitcoin/recipes/rtl.rb b/site-cookbooks/kosmos-bitcoin/recipes/rtl.rb index 118cd32..e19e829 100644 --- a/site-cookbooks/kosmos-bitcoin/recipes/rtl.rb +++ b/site-cookbooks/kosmos-bitcoin/recipes/rtl.rb @@ -3,7 +3,7 @@ # Recipe:: rtl # -node.override["kosmos_nodejs"]["version"] = "22.23.1" +node.override["kosmos_nodejs"]["version"] = "22.23.4" include_recipe 'kosmos-nodejs' app_name = "rtl" diff --git a/site-cookbooks/kosmos-hubot/recipes/botka_irc-libera-chat.rb b/site-cookbooks/kosmos-hubot/recipes/botka_irc-libera-chat.rb index 1e51b9e..a7340d4 100644 --- a/site-cookbooks/kosmos-hubot/recipes/botka_irc-libera-chat.rb +++ b/site-cookbooks/kosmos-hubot/recipes/botka_irc-libera-chat.rb @@ -48,6 +48,9 @@ end service_env = { "HUBOT_LOG_LEVEL" => node.chef_environment == "development" ? "debug" : "info", + # Node 16 bundles its own CA store, which still trusts the compromised + # eMudhra intermediate's root; use the (patched) system trust store instead. + "NODE_OPTIONS" => "--use-openssl-ca", "HUBOT_IRC_USESSL" => "true", "HUBOT_IRC_SERVER" => credentials["znc_host"], "HUBOT_IRC_PORT" => credentials["znc_port"], diff --git a/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb b/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb index 3afc8df..a33058a 100644 --- a/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb +++ b/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb @@ -69,6 +69,9 @@ end service_env = { "HUBOT_LOG_LEVEL" => node.chef_environment == "development" ? "debug" : "info", + # Node 16 bundles its own CA store, which still trusts the compromised + # eMudhra intermediate's root; use the (patched) system trust store instead. + "NODE_OPTIONS" => "--use-openssl-ca", "HUBOT_XMPP_USERNAME" => "hal8000@kosmos.org/hubot", "HUBOT_XMPP_PASSWORD" => data_bag['xmpp_password'], "HUBOT_XMPP_HOST" => "xmpp.kosmos.org", diff --git a/site-cookbooks/kosmos-ipfs/recipes/kredits_pinner.rb b/site-cookbooks/kosmos-ipfs/recipes/kredits_pinner.rb index 1c02cd8..28163a2 100644 --- a/site-cookbooks/kosmos-ipfs/recipes/kredits_pinner.rb +++ b/site-cookbooks/kosmos-ipfs/recipes/kredits_pinner.rb @@ -32,7 +32,10 @@ execute "npm install" do end service_env = { - 'DEBUG' => 'ipfs-pinner' + 'DEBUG' => 'ipfs-pinner', + # Node 16 bundles its own CA store, which still trusts the compromised + # eMudhra intermediate's root; use the (patched) system trust store instead. + 'NODE_OPTIONS' => '--use-openssl-ca' } systemd_unit "#{app_name}.service" do diff --git a/site-cookbooks/kosmos-mastodon/attributes/default.rb b/site-cookbooks/kosmos-mastodon/attributes/default.rb index 31f88e3..3704141 100644 --- a/site-cookbooks/kosmos-mastodon/attributes/default.rb +++ b/site-cookbooks/kosmos-mastodon/attributes/default.rb @@ -18,7 +18,7 @@ node.default["kosmos-mastodon"]["deploy"] = true node.default["kosmos-mastodon"]["build"] = true # Runtime versions -node.default["kosmos-mastodon"]["nodejs_version"] = "24.21.0" +node.default["kosmos-mastodon"]["nodejs_version"] = "24.22.1" node.default["kosmos-mastodon"]["ruby_version"] = "4.0.7" node.default["kosmos-mastodon"]["ruby_build_version"] = "v20260924" diff --git a/site-cookbooks/kosmos-nodejs/recipes/default.rb b/site-cookbooks/kosmos-nodejs/recipes/default.rb index 2769bdc..a01c622 100644 --- a/site-cookbooks/kosmos-nodejs/recipes/default.rb +++ b/site-cookbooks/kosmos-nodejs/recipes/default.rb @@ -7,3 +7,11 @@ nodejs_install 'nodejs' do install_method 'package' version node["kosmos_nodejs"]["version"] end + +# nodejs_install only uses the version to select the Nodesource repo major, then +# runs a bare `package nodejs`, which Chef considers up to date for an already +# installed package. Force an upgrade so a newer patch of the same major (e.g. +# 22.23.1 -> 22.23.4) is actually installed. +package 'nodejs' do + action :upgrade +end diff --git a/site-cookbooks/kredits-github/recipes/default.rb b/site-cookbooks/kredits-github/recipes/default.rb index 49fffe1..d37ba9e 100644 --- a/site-cookbooks/kredits-github/recipes/default.rb +++ b/site-cookbooks/kredits-github/recipes/default.rb @@ -44,7 +44,10 @@ service_env = { "LOG_LEVEL" => 'info', "APP_ID" => credentials["app_id"], "PRIVATE_KEY_PATH" => "#{app_path}/github_app_key.pem", - "WEBHOOK_SECRET" => credentials["webhook_secret"] + "WEBHOOK_SECRET" => credentials["webhook_secret"], + # Node 16 bundles its own CA store, which still trusts the compromised + # eMudhra intermediate's root; use the (patched) system trust store instead. + "NODE_OPTIONS" => "--use-openssl-ca" } systemd_unit "#{app_name}.service" do diff --git a/site-cookbooks/sockethub/recipes/default.rb b/site-cookbooks/sockethub/recipes/default.rb index 8eb4beb..5a86cdc 100644 --- a/site-cookbooks/sockethub/recipes/default.rb +++ b/site-cookbooks/sockethub/recipes/default.rb @@ -3,7 +3,7 @@ # Recipe:: default # -node.override["kosmos_nodejs"]["version"] = "24.21.0" +node.override["kosmos_nodejs"]["version"] = "24.22.1" include_recipe 'kosmos-nodejs' user = "sockethub" @@ -63,6 +63,7 @@ ExecStart=#{entry} User=#{user} Group=#{group} Environment=SOCKETHUB_CONFIG=/etc/sockethub/sockethub.config.json +Environment=NODE_OPTIONS=--use-openssl-ca Restart=always [Install] From fc55b0e062aa665ea6bd6888910ac6186948abbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sat, 24 Oct 2026 15:29:30 +0200 Subject: [PATCH 05/18] Upgrade RTL to v0.15.15 Pin v0.15.15 and set blockExplorerUrl (config-only since 0.15.14). Discard local checkout changes before the pinned checkout, and use `npm ci --omit=dev --legacy-peer-deps` per upstream guidance. --- .../kosmos-bitcoin/attributes/default.rb | 3 ++- site-cookbooks/kosmos-bitcoin/recipes/rtl.rb | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/site-cookbooks/kosmos-bitcoin/attributes/default.rb b/site-cookbooks/kosmos-bitcoin/attributes/default.rb index 84a081b..f7763f8 100644 --- a/site-cookbooks/kosmos-bitcoin/attributes/default.rb +++ b/site-cookbooks/kosmos-bitcoin/attributes/default.rb @@ -60,9 +60,10 @@ node.default['lnd']['tor'] = { } node.default['rtl']['repo'] = 'https://github.com/Ride-The-Lightning/RTL.git' -node.default['rtl']['revision'] = 'v0.15.2' +node.default['rtl']['revision'] = 'v0.15.15' node.default['rtl']['host'] = '10.1.1.163' node.default['rtl']['port'] = '3000' +node.default['rtl']['block_explorer_url'] = 'https://mempool.space' node.default['lndhub-go']['repo'] = 'https://github.com/getAlby/lndhub.go.git' node.default['lndhub-go']['revision'] = '1.0.2' diff --git a/site-cookbooks/kosmos-bitcoin/recipes/rtl.rb b/site-cookbooks/kosmos-bitcoin/recipes/rtl.rb index e19e829..867fb4f 100644 --- a/site-cookbooks/kosmos-bitcoin/recipes/rtl.rb +++ b/site-cookbooks/kosmos-bitcoin/recipes/rtl.rb @@ -16,6 +16,9 @@ credentials = Chef::EncryptedDataBagItem.load('credentials', 'rtl') rtl_config = { host: node['rtl']['host'], port: node['rtl']['port'], + # Since RTL 0.15.14 the block explorer URL can only be set here (or via + # BLOCK_EXPLORER_URL); it is no longer settable from the settings UI. + blockExplorerUrl: node['rtl']['block_explorer_url'], defaultNodeIndex: 1, SSO: { rtlSSO: 0, @@ -46,6 +49,17 @@ rtl_config = { multiPassHashed: credentials["multiPassHashed"] } +# Discard any local modifications (e.g. a rewritten package-lock.json) so the +# checkout below to the pinned revision succeeds. +execute "reset rtl checkout" do + command "git checkout -- ." + cwd rtl_dir + user bitcoin_user + group bitcoin_group + environment "HOME" => rtl_dir + only_if { ::File.directory?("#{rtl_dir}/.git") } +end + git rtl_dir do user bitcoin_user group bitcoin_group @@ -59,8 +73,8 @@ execute "npm_install" do cwd rtl_dir environment "HOME" => rtl_dir user bitcoin_user - # TODO remove --force when upstream dependency issues have been resolved - command "npm install --force" + # Clean install from the committed lockfile, as recommended since RTL 0.15.6. + command "npm ci --omit=dev --legacy-peer-deps" action :nothing end From bd4a6501ab6c1ee08f5d69f5ee6ec4e86533cd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sat, 24 Oct 2026 15:29:34 +0200 Subject: [PATCH 06/18] Update Ubuntu base image to release-20261017 Refresh the noble cloud image so newly created VMs start with fewer pending package upgrades. Old base images are left in place. --- site-cookbooks/kosmos_kvm/attributes/default.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site-cookbooks/kosmos_kvm/attributes/default.rb b/site-cookbooks/kosmos_kvm/attributes/default.rb index 84a66f3..3e40850 100644 --- a/site-cookbooks/kosmos_kvm/attributes/default.rb +++ b/site-cookbooks/kosmos_kvm/attributes/default.rb @@ -1,9 +1,9 @@ -release = "20260801" +release = "20261017" img_filename = "ubuntu-24.04-server-cloudimg-amd64" node.default["kosmos_kvm"]["host"]["qemu_base_image"] = { "url" => "https://cloud-images.ubuntu.com/releases/noble/release-#{release}/#{img_filename}.img", - "checksum" => "0533b0655c32e68b31d792ecd6ccfca95abdbc536c4446874fe0513bd4140ffe", + "checksum" => "04e8f2461a16a44e78a370284471f29616cdd9baf1893df25807b71f985b6c94", "path" => "/var/lib/libvirt/images/base/#{img_filename}-#{release}.qcow2" } From 44f86d3f07ed54684693776c80d10fe2ee44cd46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sat, 24 Oct 2026 15:55:36 +0200 Subject: [PATCH 07/18] Serve kredits-github via openresty, on Node 22 Move kredits-github onto Node 22.23.4 and behind the openresty reverse proxy on the proxy hosts, replacing its own nginx + the custom haproxy entry. - pin Node 22.23.4; run the app with HOST=0.0.0.0 / NODE_ENV=production and open port 3000 to 10.1.1.0/24 so the proxy can reach it over ZeroTier - create /opt/kredits-github owned by the deploy user before cloning - add kredits-github::nginx_proxy (tls_cert_for + openresty_site, upstream from role:kredits_github) and register it in the openresty_proxy run list - drop the app-node nginx recipe/template and its role entry --- roles/kredits_github.rb | 1 - roles/openresty_proxy.rb | 1 + site-cookbooks/kredits-github/metadata.rb | 4 +- .../kredits-github/recipes/default.rb | 28 +++++++++-- .../kredits-github/recipes/nginx.rb | 46 ------------------- .../kredits-github/recipes/nginx_proxy.rb | 30 ++++++++++++ .../templates/default/nginx_conf.erb | 27 ----------- .../nginx_conf_kredits_github_proxy.erb | 24 ++++++++++ 8 files changed, 82 insertions(+), 79 deletions(-) delete mode 100644 site-cookbooks/kredits-github/recipes/nginx.rb create mode 100644 site-cookbooks/kredits-github/recipes/nginx_proxy.rb delete mode 100644 site-cookbooks/kredits-github/templates/default/nginx_conf.erb create mode 100644 site-cookbooks/kredits-github/templates/default/nginx_conf_kredits_github_proxy.erb diff --git a/roles/kredits_github.rb b/roles/kredits_github.rb index 57f3d88..0c8b7c0 100644 --- a/roles/kredits_github.rb +++ b/roles/kredits_github.rb @@ -2,5 +2,4 @@ name "kredits_github" run_list %w( kredits-github::default - kredits-github::nginx ) diff --git a/roles/openresty_proxy.rb b/roles/openresty_proxy.rb index b6b8eaf..c13bbfd 100644 --- a/roles/openresty_proxy.rb +++ b/roles/openresty_proxy.rb @@ -38,6 +38,7 @@ production_run_list = %w( kosmos-hubot::nginx_botka_irc-libera-chat kosmos-hubot::nginx_hal8000_xmpp kosmos-ipfs::nginx_public_gateway + kredits-github::nginx_proxy kosmos-mastodon::nginx remotestorage_discourse::nginx sockethub::nginx diff --git a/site-cookbooks/kredits-github/metadata.rb b/site-cookbooks/kredits-github/metadata.rb index 12d54b6..b208064 100644 --- a/site-cookbooks/kredits-github/metadata.rb +++ b/site-cookbooks/kredits-github/metadata.rb @@ -6,5 +6,7 @@ description 'Installs/Configures kredits-github' long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) version '0.1.0' +depends 'firewall' +depends 'kosmos-base' depends 'kosmos-nodejs' -depends 'kosmos-nginx' +depends 'kosmos_openresty' diff --git a/site-cookbooks/kredits-github/recipes/default.rb b/site-cookbooks/kredits-github/recipes/default.rb index d37ba9e..5316e44 100644 --- a/site-cookbooks/kredits-github/recipes/default.rb +++ b/site-cookbooks/kredits-github/recipes/default.rb @@ -2,6 +2,7 @@ # Cookbook Name:: kredits-github # Recipe:: default +node.override["kosmos_nodejs"]["version"] = "22.23.4" include_recipe 'kosmos-nodejs' app_name = "kredits-github" @@ -19,6 +20,13 @@ user deploy_user do end app_path = "/opt/#{app_name}" + +directory app_path do + owner deploy_user + group deploy_group + mode "0755" +end + git app_path do user deploy_user group deploy_group @@ -42,12 +50,13 @@ end service_env = { "LOG_LEVEL" => 'info', + "NODE_ENV" => 'production', + # Bind on all interfaces so the openresty reverse proxy (draco/fornax) can + # reach the app over ZeroTier; port 3000 is firewalled to 10.1.1.0/24 below. + "HOST" => '0.0.0.0', "APP_ID" => credentials["app_id"], "PRIVATE_KEY_PATH" => "#{app_path}/github_app_key.pem", - "WEBHOOK_SECRET" => credentials["webhook_secret"], - # Node 16 bundles its own CA store, which still trusts the compromised - # eMudhra intermediate's root; use the (patched) system trust store instead. - "NODE_OPTIONS" => "--use-openssl-ca" + "WEBHOOK_SECRET" => credentials["webhook_secret"] } systemd_unit "#{app_name}.service" do @@ -73,3 +82,14 @@ systemd_unit "#{app_name}.service" do triggers_reload true action [:create, :enable, :start] end + +unless node.chef_environment == "development" + include_recipe "kosmos-base::firewall" + + firewall_rule 'kredits-github' do + port node['kredits-github']['port'].to_i + source '10.1.1.0/24' + protocol :tcp + command :allow + end +end diff --git a/site-cookbooks/kredits-github/recipes/nginx.rb b/site-cookbooks/kredits-github/recipes/nginx.rb deleted file mode 100644 index 54b576a..0000000 --- a/site-cookbooks/kredits-github/recipes/nginx.rb +++ /dev/null @@ -1,46 +0,0 @@ -# -# Cookbook Name:: kredits-github -# Recipe:: nginx -# -# The MIT License (MIT) -# -# Copyright:: 2019, Kosmos Developers -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -include_recipe 'kosmos-nginx' -server_name = node['kredits-github']['domain'] - -template "#{node['nginx']['dir']}/sites-available/#{server_name}" do - source 'nginx_conf.erb' - owner 'www-data' - mode 0640 - variables app_name: "kredits-github", - nodejs_port: node['kredits-github']['port'], - server_name: server_name, - ssl_cert: "/etc/letsencrypt/live/#{server_name}/fullchain.pem", - ssl_key: "/etc/letsencrypt/live/#{server_name}/privkey.pem" - notifies :reload, 'service[nginx]', :delayed -end - -nginx_site server_name do - action :enable -end - -nginx_certbot_site server_name diff --git a/site-cookbooks/kredits-github/recipes/nginx_proxy.rb b/site-cookbooks/kredits-github/recipes/nginx_proxy.rb new file mode 100644 index 0000000..5b3dcfb --- /dev/null +++ b/site-cookbooks/kredits-github/recipes/nginx_proxy.rb @@ -0,0 +1,30 @@ +# +# Cookbook Name:: kredits-github +# Recipe:: nginx_proxy +# +# Terminates TLS and proxies kredits-github.kosmos.org to the app node +# (role:kredits_github) over the ZeroTier network. Runs on the proxy hosts. +# + +domain = node['kredits-github']['domain'] + +kredits_node = search(:node, 'role:kredits_github').first + +if kredits_node.nil? + Chef::Log.warn("No node found with 'kredits_github' role. Not configuring openresty site.") + return +end + +tls_cert_for domain do + auth 'gandi_dns' + action :create +end + +openresty_site domain do + template 'nginx_conf_kredits_github_proxy.erb' + variables domain: domain, + upstream_host: kredits_node['knife_zero']['host'], + upstream_port: node['kredits-github']['port'], + ssl_cert: "/etc/letsencrypt/live/#{domain}/fullchain.pem", + ssl_key: "/etc/letsencrypt/live/#{domain}/privkey.pem" +end diff --git a/site-cookbooks/kredits-github/templates/default/nginx_conf.erb b/site-cookbooks/kredits-github/templates/default/nginx_conf.erb deleted file mode 100644 index 5d8d496..0000000 --- a/site-cookbooks/kredits-github/templates/default/nginx_conf.erb +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Chef -upstream _<%= @app_name %> { - server localhost:<%= @nodejs_port %>; -} - -<% if File.exist?(@ssl_cert) && File.exist?(@ssl_key) -%> -server { - listen <%= "#{node['openresty']['listen_ip']}:" if node['openresty']['listen_ip'] %>443 ssl http2; - listen <%= "[#{node['openresty']['listen_ipv6']}]" %>:443 ssl http2; - server_name <%= @server_name %>; - - access_log <%= node[:nginx][:log_dir] %>/<%= @app_name %>.access.log json; - error_log <%= node[:nginx][:log_dir] %>/<%= @app_name %>.error.log warn; - - gzip on; - - add_header Strict-Transport-Security "max-age=15768000"; - - location / { - proxy_buffers 1024 8k; # Increase number of buffers. Default is 8 - proxy_pass http://_<%= @app_name %>; - } - - ssl_certificate <%= @ssl_cert %>; - ssl_certificate_key <%= @ssl_key %>; -} -<% end -%> diff --git a/site-cookbooks/kredits-github/templates/default/nginx_conf_kredits_github_proxy.erb b/site-cookbooks/kredits-github/templates/default/nginx_conf_kredits_github_proxy.erb new file mode 100644 index 0000000..08e48b3 --- /dev/null +++ b/site-cookbooks/kredits-github/templates/default/nginx_conf_kredits_github_proxy.erb @@ -0,0 +1,24 @@ +upstream _kredits_github { + server <%= @upstream_host %>:<%= @upstream_port %>; +} + +server { + server_name <%= @domain %>; + listen <%= "#{node['openresty']['listen_ip']}:" if node['openresty']['listen_ip'] %>443 ssl http2; + listen <%= "[#{node['openresty']['listen_ipv6']}]" %>:443 ssl http2; + + access_log "<%= node['openresty']['log_dir'] %>/<%= @domain %>.access.log" json; + error_log "<%= node['openresty']['log_dir'] %>/<%= @domain %>.error.log"; + + ssl_certificate <%= @ssl_cert %>; + ssl_certificate_key <%= @ssl_key %>; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://_kredits_github; + proxy_http_version 1.1; + } +} From 552427a10beb3deb20a2349a4218f6d854dd7404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sat, 24 Oct 2026 16:28:28 +0200 Subject: [PATCH 08/18] Track nodejs-5 and the kredits-github migration Update the nodes submodule (nodejs-5 added, kredits-github moved off nodejs-4) and add the nodejs-5 client. --- clients/nodejs-5.json | 4 ++++ nodes | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 clients/nodejs-5.json diff --git a/clients/nodejs-5.json b/clients/nodejs-5.json new file mode 100644 index 0000000..1fb2904 --- /dev/null +++ b/clients/nodejs-5.json @@ -0,0 +1,4 @@ +{ + "name": "nodejs-5", + "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxI1x2klSMk711UoLzW6I\nX42v+rw7tLlW7jX6zafL10KxRLdJN5Vz+0baF6gesRuWaq8kh9xAECkove5V4CsL\n867lok+gfitdOuk30Vs72UrGXyqJ12zTRHevwVz35Sf6W8swJ/UF3Oe+JyBjJkEv\nuBRSlMdfXI69oH5GV+V5Bh6AZHAsddSZQQbp/ph+KlsZ1f09Sv1W1zSkkf/7yOYk\nFBjE1zOLWoVbA63wuUmkq9LzwM4D8D+JY9TjvmkvyLVKM5wfaEAD04E/OLo1jYlF\nPX8W4RNOZeROQZzRcLwtUwuX/6yLZFjlkcbr+WRNV7uXjyrL7qoJ6umf2pkqPrIX\nIQIDAQAB\n-----END PUBLIC KEY-----\n" +} \ No newline at end of file diff --git a/nodes b/nodes index 58e8fb8..e37d92d 160000 --- a/nodes +++ b/nodes @@ -1 +1 @@ -Subproject commit 58e8fb83fd96299723bfd8ca63b393eede8fd67a +Subproject commit e37d92dd6cfac5bf6fb8635fef95c1568b15bef5 From 93fd33a3efa8c77c80f03aae986a3dcdf14cb56a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 27 Oct 2026 21:07:04 +0400 Subject: [PATCH 09/18] Remove host config for xmpp bots --- site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb b/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb index a33058a..6c8b2d7 100644 --- a/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb +++ b/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb @@ -74,7 +74,6 @@ service_env = { "NODE_OPTIONS" => "--use-openssl-ca", "HUBOT_XMPP_USERNAME" => "hal8000@kosmos.org/hubot", "HUBOT_XMPP_PASSWORD" => data_bag['xmpp_password'], - "HUBOT_XMPP_HOST" => "xmpp.kosmos.org", "HUBOT_XMPP_ROOMS" => node[app_name]['rooms'].join(','), "HUBOT_AUTH_ADMIN" => node[app_name]['auth_admins'].join(','), "HUBOT_RSS_PRINTSUMMARY" => "false", From b04e2e153a61a5b5c2ef6ae77e799bc3fb312b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 28 Oct 2026 13:45:05 +0400 Subject: [PATCH 10/18] Remove the dormant wormhole bot wormhole has no run-list entry on any node; drop its recipe and data bag. --- data_bags/credentials/wormhole.json | 17 ---- .../kosmos-hubot/recipes/wormhole.rb | 86 ------------------- 2 files changed, 103 deletions(-) delete mode 100644 data_bags/credentials/wormhole.json delete mode 100644 site-cookbooks/kosmos-hubot/recipes/wormhole.rb diff --git a/data_bags/credentials/wormhole.json b/data_bags/credentials/wormhole.json deleted file mode 100644 index 61f469e..0000000 --- a/data_bags/credentials/wormhole.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "wormhole", - "nickserv_password": { - "encrypted_data": "cFY94NjfcqeBFUpPhg9PRJpty1x2Z9mWG0YBzv/jvao=\n", - "iv": "9CAeYa4XnwXjOtiO\n", - "auth_tag": "rjY9GyetAOkqpCuiFBdkag==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "xmpp_password": { - "encrypted_data": "d8H7G+ua0lYMIvAEMJ6FRAShsNsPqcfZd7TRfzaGSbg=\n", - "iv": "qYvOpTVigw4fZPA/\n", - "auth_tag": "/yYPrlS+n7ElYn7BC+5Fzg==\n", - "version": 3, - "cipher": "aes-256-gcm" - } -} \ No newline at end of file diff --git a/site-cookbooks/kosmos-hubot/recipes/wormhole.rb b/site-cookbooks/kosmos-hubot/recipes/wormhole.rb deleted file mode 100644 index f502ee6..0000000 --- a/site-cookbooks/kosmos-hubot/recipes/wormhole.rb +++ /dev/null @@ -1,86 +0,0 @@ -# -# Cookbook Name:: kosmos-hubot -# Recipe:: wormhole -# - -app_name = "wormhole" -app_user = "hubot" -app_group = "hubot" -app_path = "/opt/#{app_name}" -data_bag = Chef::EncryptedDataBagItem.load('credentials', app_name) - -app_env = { - "HUBOT_LOG_LEVEL" => node.chef_environment == "development" ? "debug" : "info", - "HUBOT_IRC_SERVER" => "irc.freenode.net", - "HUBOT_IRC_ROOMS" => "#kosmos,#kosmos-dev,#kosmos-random,#sockethub", - "HUBOT_IRC_NICK" => app_name, - "HUBOT_IRC_NICKSERV_USERNAME" => app_name, - "HUBOT_IRC_NICKSERV_PASSWORD" => data_bag['nickserv_password'], - "HUBOT_IRC_UNFLOOD" => "100", - "HUBOT_WORMHOLE_XMPP_JID" => "wormhole@kosmos.org", - "HUBOT_WORMHOLE_XMPP_PASSWORD" => data_bag['xmpp_password'], - "HUBOT_WORMHOLE_XMPP_HOST" => "xmpp.kosmos.org", - "HUBOT_WORMHOLE_XMPP_PORT" => "5222", - "HUBOT_WORMHOLE_XMPP_NICKNAME" => "wormhole", - "HUBOT_WORMHOLE_XMPP_ROOMS" => "kosmos@kosmos.chat,kosmos-dev@kosmos.chat,kosmos-random@kosmos.chat,sockethub@kosmos.chat", - "EXPRESS_PORT" => node['wormhole']['http_port'] -} - -build_essential -include_recipe "kosmos-nodejs" -include_recipe "kosmos-hubot::_user" -include_recipe "git" - -git app_path do - user app_user - group app_group - repository "https://gitea.kosmos.org/kosmos/wormhole.git" - revision "master" - action :sync - notifies :run, "bash[npm_install_#{app_name}]", :immediately - notifies :restart, "systemd_unit[#{app_name}.service]", :delayed -end - -bash "npm_install_#{app_name}" do - user app_user - cwd app_path - code 'npm install' - action :nothing -end - -file "#{app_path}/external-scripts.json" do - mode "0640" - owner app_user - group app_group - content [].to_json -end - -systemd_unit "#{app_name}.service" do - content({ - Unit: { - Description: "#{app_name} (node.js app)", - Documentation: ['https://gitea.kosmos.org/kosmos/wormhole'], - }, - Service: { - User: app_user, - Group: app_group, - ExecStart: "#{app_path}/bin/hubot -a irc --name #{app_name}", - WorkingDirectory: app_path, - Environment: app_env.map{|k, v| "'#{k}=#{v}'"}.join(' '), - Type: 'simple', - Restart: 'always', - RestartSec: '2', - TimeoutSec: '10', - PrivateTmp: true, - ProtectSystem: 'full', - NoNewPrivileges: true, - PrivateDevices: true - }, - Install: { - WantedBy: 'multi-user.target' - } - }) - verify false - triggers_reload true - action [:create, :enable, :start] -end From cc9b324a18080f018146adfdc79526cf4ad52a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 28 Oct 2026 13:45:18 +0400 Subject: [PATCH 11/18] Refactor kosmos-hubot to a data-driven model Replace the per-bot and per-vhost recipes with a single data-driven setup. - resources/bot.rb (kosmos_hubot_bot): git checkout, npm install, generated external-scripts.json, extra files, and one hardened systemd unit for every hubot (Type=simple, Restart=always, RestartSec=2, TimeoutSec=10, PrivateTmp, ProtectSystem=full, NoNewPrivileges, PrivateDevices) - recipes/default.rb: shared setup + loop over node['kosmos-hubot']['bots']; recipes/nginx.rb: loop over node['kosmos-hubot']['vhosts']; recipes/hal8000_extras.rb: augments the hal8000 bot (searched IPFS/RSK hosts, kredits .env, wallet) and declares no resources itself - bot config and vhosts now live in attributes; secrets come from the encrypted credentials/hubot bag via secret_env - Node 22.23.4; kosmos_redis replaces redisio; REDIS_URL uses ?; the adapter is passed as -a ; botka gains hubot-incoming-webhook - roles/hubot, nginx_proxy, openresty_proxy and the chef-nodes pointer updated --- nodes | 2 +- roles/hubot.rb | 3 +- roles/nginx_proxy.rb | 3 +- roles/openresty_proxy.rb | 3 +- site-cookbooks/kosmos-hubot/README.md | 29 +++- .../kosmos-hubot/attributes/default.rb | 130 ++++++++++++---- site-cookbooks/kosmos-hubot/metadata.rb | 2 +- .../kosmos-hubot/recipes/_nodejs.rb | 2 +- .../recipes/botka_irc-libera-chat.rb | 111 -------------- .../kosmos-hubot/recipes/default.rb | 36 +++++ .../kosmos-hubot/recipes/hal8000_extras.rb | 48 ++++++ .../kosmos-hubot/recipes/hal8000_xmpp.rb | 140 ------------------ site-cookbooks/kosmos-hubot/recipes/nginx.rb | 26 ++++ .../recipes/nginx_botka_irc-libera-chat.rb | 18 --- .../recipes/nginx_hal8000_xmpp.rb | 19 --- site-cookbooks/kosmos-hubot/resources/bot.rb | 131 ++++++++++++++++ 16 files changed, 373 insertions(+), 330 deletions(-) delete mode 100644 site-cookbooks/kosmos-hubot/recipes/botka_irc-libera-chat.rb create mode 100644 site-cookbooks/kosmos-hubot/recipes/default.rb create mode 100644 site-cookbooks/kosmos-hubot/recipes/hal8000_extras.rb delete mode 100644 site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb create mode 100644 site-cookbooks/kosmos-hubot/recipes/nginx.rb delete mode 100644 site-cookbooks/kosmos-hubot/recipes/nginx_botka_irc-libera-chat.rb delete mode 100644 site-cookbooks/kosmos-hubot/recipes/nginx_hal8000_xmpp.rb create mode 100644 site-cookbooks/kosmos-hubot/resources/bot.rb diff --git a/nodes b/nodes index e37d92d..afffa40 160000 --- a/nodes +++ b/nodes @@ -1 +1 @@ -Subproject commit e37d92dd6cfac5bf6fb8635fef95c1568b15bef5 +Subproject commit afffa4003116baee16fe86539d372c5c63d62e4f diff --git a/roles/hubot.rb b/roles/hubot.rb index f13d5b0..b11b538 100644 --- a/roles/hubot.rb +++ b/roles/hubot.rb @@ -1,8 +1,7 @@ name "hubot" default_run_list = %w( - kosmos-hubot::botka_irc-libera-chat - kosmos-hubot::hal8000_xmpp + kosmos-hubot::default ) env_run_lists( diff --git a/roles/nginx_proxy.rb b/roles/nginx_proxy.rb index bc6cc58..81ef352 100644 --- a/roles/nginx_proxy.rb +++ b/roles/nginx_proxy.rb @@ -29,8 +29,7 @@ default_run_list = %w( kosmos-akkounts::nginx_api kosmos-bitcoin::nginx_lndhub kosmos-ejabberd::nginx - kosmos-hubot::nginx_botka_irc-libera-chat - kosmos-hubot::nginx_hal8000_xmpp + kosmos-hubot::nginx kosmos-ipfs::nginx_public_gateway kosmos-mastodon::nginx remotestorage_discourse::nginx diff --git a/roles/openresty_proxy.rb b/roles/openresty_proxy.rb index c13bbfd..cb41bc5 100644 --- a/roles/openresty_proxy.rb +++ b/roles/openresty_proxy.rb @@ -35,8 +35,7 @@ production_run_list = %w( kosmos-akkounts::nginx kosmos-akkounts::nginx_api kosmos-bitcoin::nginx_lndhub - kosmos-hubot::nginx_botka_irc-libera-chat - kosmos-hubot::nginx_hal8000_xmpp + kosmos-hubot::nginx kosmos-ipfs::nginx_public_gateway kredits-github::nginx_proxy kosmos-mastodon::nginx diff --git a/site-cookbooks/kosmos-hubot/README.md b/site-cookbooks/kosmos-hubot/README.md index 7781499..630613b 100644 --- a/site-cookbooks/kosmos-hubot/README.md +++ b/site-cookbooks/kosmos-hubot/README.md @@ -1,4 +1,31 @@ kosmos-hubot Cookbook ===================== -This cookbook sets up our hubots +Sets up our [hubot](https://hubot.github.com/) bots. + +## Layout + +- `attributes/default.rb` — declares every bot in + `node['kosmos-hubot']['bots']` (keyed by `app_name`) and the openresty + vhosts in `node['kosmos-hubot']['vhosts']`. +- `resources/bot.rb` — the `kosmos_hubot_bot` resource: git checkout, + `npm install`, generated `external-scripts.json`, extra files, a hardened + systemd unit, and an optional firewall rule. +- `recipes/default.rb` — shared setup (`build_essential`, git, the `hubot` + user, Node, Redis, firewall) and a loop over the bots. +- `recipes/nginx.rb` — creates the vhosts on the proxy nodes. +- `recipes/hal8000_extras.rb` — augments the hal8000 bot's config (searched + IPFS/RSK hosts, kredits `.env`, wallet); included by `recipes/default.rb` + before the bot is declared. + +## Adding a bot + +Add an entry to `node['kosmos-hubot']['bots']` (repository, adapter, +robot_name, http_port, external_scripts, env/secret_env, …) and, if it exposes +an HTTP endpoint, a vhost entry. No recipe, role or node changes required. + +## Secrets + +Service environment secrets come from the encrypted `credentials/hubot` data +bag, keyed by `app_name`. `secret_env` maps a service env var to a key within +that bot's hash. diff --git a/site-cookbooks/kosmos-hubot/attributes/default.rb b/site-cookbooks/kosmos-hubot/attributes/default.rb index bfac99f..5f696be 100644 --- a/site-cookbooks/kosmos-hubot/attributes/default.rb +++ b/site-cookbooks/kosmos-hubot/attributes/default.rb @@ -1,36 +1,102 @@ -node.default['hal8000']['http_port'] = 8080 +# Per-bot configuration for the hubot role. +# +# Each entry is keyed by app_name and consumed by the `kosmos_hubot_bot` +# resource. Secrets are resolved from the encrypted `credentials/hubot` data +# bag, keyed by app_name; `secret_env` maps a service env var to a key within +# that bot's hash. `adapter` is passed to hubot as `-a` (the adapter module +# name, e.g. "@hubot-friends/hubot-irc" or "hubot-xmpp"). -node.default['botka_irc-libera-chat']['http_port'] = 8081 +default['kosmos-hubot']['bots'] = { + 'botka_irc-libera-chat' => { + 'repository' => 'https://gitea.kosmos.org/kosmos/botka.git', + 'revision' => 'master', + 'adapter' => '@hubot-friends/hubot-irc', + 'robot_name' => 'botka', + 'http_port' => 8081, + 'redis' => true, + 'firewall' => { 'port' => 8081, 'source' => '10.1.1.0/24' }, + 'external_scripts' => %w[ + hubot-help hubot-incoming-webhook hubot-redis-brain + hubot-remotestorage-logger + ], + 'secret_env' => { + 'HUBOT_IRC_SERVER' => 'znc_host', + 'HUBOT_IRC_PORT' => 'znc_port', + 'HUBOT_IRC_USERNAME' => 'znc_user', + 'HUBOT_IRC_PASSWORD' => 'znc_password', + 'WEBHOOK_TOKEN' => 'webhook_token', + 'RS_LOGGER_TOKEN' => 'rs_logger_token' + }, + 'env' => { + 'HUBOT_IRC_USESSL' => 'true', + 'HUBOT_IRC_NICK' => 'botka', + 'HUBOT_IRC_REALNAME' => 'botka (kosmos)', + 'HUBOT_IRC_ROOMS' => '#kosmos,#kosmos-dev,#kosmos-random,#remotestorage,#hackerbeach,#unhosted,#sockethub,#mastodon', + 'HUBOT_IRC_UNFLOOD' => '100', + 'HUBOT_AUTH_ADMIN' => 'bkero,raucao', + 'HUBOT_HELP_REPLY_IN_PRIVATE' => 'true', + 'REDIS_URL' => 'redis://localhost:6379?botka', + 'RS_LOGGER_USER' => 'kosmos@5apps.com', + 'RS_LOGGER_SERVER_NAME' => 'irc.libera.chat', + 'RS_LOGGER_PUBLIC' => 'true' + } + }, -node.default['hal8000_xmpp']['http_port'] = 8082 -node.default['hal8000_xmpp']['domain'] = "hal8000.kosmos.chat" + 'hal8000_xmpp' => { + 'repository' => 'https://gitea.kosmos.org/kosmos/hal8000.git', + 'revision' => 'master', + 'extras' => 'hal8000_extras', + 'adapter' => 'hubot-xmpp', + 'robot_name' => 'hal8000', + 'http_port' => 8082, + 'redis' => true, + 'firewall' => { 'port' => 8082, 'source' => '10.1.1.0/24' }, + 'external_scripts' => %w[ + hubot-help hubot-redis-brain hubot-rules hubot-incoming-webhook + hubot-auth @kredits/hubot-kredits + ], + 'secret_env' => { + 'HUBOT_XMPP_PASSWORD' => 'xmpp_password', + 'WEBHOOK_TOKEN' => 'webhook_token', + 'KREDITS_WEBHOOK_TOKEN' => 'kredits_webhook_token', + 'KREDITS_WALLET_PASSWORD' => 'kredits_wallet_password', + 'KREDITS_SESSION_SECRET' => 'kredits_session_secret', + 'KREDITS_GITHUB_KEY' => 'kredits_github_key', + 'KREDITS_GITHUB_SECRET' => 'kredits_github_secret', + 'KREDITS_ZOOM_JWT' => 'kredits_zoom_jwt' + }, + 'env' => { + 'HUBOT_XMPP_USERNAME' => 'hal8000@kosmos.org/hubot', + 'HUBOT_XMPP_ROOMS' => 'kosmos@kosmos.chat,kosmos-dev@kosmos.chat,kredits@kosmos.chat,info@kosmos.chat,ops@kosmos.chat', + 'HUBOT_AUTH_ADMIN' => '', + 'HUBOT_HELP_REPLY_IN_PRIVATE' => 'true', + 'HUBOT_RSS_PRINTSUMMARY' => 'false', + 'HUBOT_RSS_PRINTERROR' => 'false', + 'HUBOT_RSS_IRCCOLORS' => 'true', + 'HUBOT_RSS_HEADER' => 'Update:', + 'HUBOT_PLUSPLUS_POINTS_TERM' => 'karma,karma', + 'REDIS_URL' => 'redis://localhost:6379?hal8000_xmpp', + # IPFS_API_HOST and KREDITS_PROVIDER_URL are resolved by the default + # recipe from node searches (ipfs_gateway / rskj_testnet). + 'IPFS_API_HOST' => 'localhost', + 'IPFS_API_PORT' => '5001', + 'IPFS_API_PROTOCOL' => 'http', + 'KREDITS_PROVIDER_URL' => 'https://rsk-testnet.kosmos.org', + 'KREDITS_WEB_URL' => 'https://kredits.kosmos.org', + 'KREDITS_ROOM' => 'kredits@kosmos.chat', + 'KREDITS_WALLET_PATH' => 'wallet.json', + 'KREDITS_MEDIAWIKI_URL' => 'https://wiki.kosmos.org/', + 'KREDITS_GITHUB_REPO_BLACKLIST' => '67P/test-one-two', + 'KREDITS_GITEA_REPO_BLACKLIST' => 'kosmos/test-one-two', + 'KREDITS_GRANT_HOST' => 'hal8000.kosmos.chat', + 'KREDITS_GRANT_PROTOCOL' => 'https', + 'KREDITS_ZOOM_MEETING_WHITELIST' => '414901303,82557072771' + } + } +} -node.default['wormhole']['http_port'] = 8083 - -node.default['hal8000_xmpp']['hubot_scripts'] = [ - "hubot-help", "hubot-redis-brain", "hubot-rules", "hubot-shipit", - "hubot-tell", "hubot-seen", "hubot-incoming-webhook", - "hubot-auth", "hubot-schedule", "@kredits/hubot-kredits" +# nginx/openresty vhosts that front the bots' HTTP endpoints (webhooks, health). +default['kosmos-hubot']['vhosts'] = [ + { 'domain' => 'irc-libera-chat.botka.kosmos.chat', 'port' => 8081 }, + { 'domain' => 'hal8000.kosmos.chat', 'port' => 8082 } ] - -node.default['hal8000_xmpp']['rooms'] = [ - 'kosmos@kosmos.chat', - 'kosmos-dev@kosmos.chat', - 'kredits@kosmos.chat', - 'info@kosmos.chat', - 'ops@kosmos.chat', -] - -node.default['hal8000_xmpp']['auth_admins'] = [] - -node.default['hal8000_xmpp']['kredits']['ipfs_host'] = 'localhost' -node.default['hal8000_xmpp']['kredits']['ipfs_port'] = '5001' -node.default['hal8000_xmpp']['kredits']['ipfs_protocol'] = 'http' -node.default['hal8000_xmpp']['kredits']['room'] = 'kredits@kosmos.chat' -node.default['hal8000_xmpp']['kredits']['provider_url'] = 'https://rsk-testnet.kosmos.org' -node.default['hal8000_xmpp']['kredits']['network_id'] = '31' -node.default['hal8000_xmpp']['kredits']['wallet_path'] = 'wallet.json' -node.default['hal8000_xmpp']['kredits']['mediawiki_url'] = 'https://wiki.kosmos.org/' -node.default['hal8000_xmpp']['kredits']['github_repo_blacklist'] = '67P/test-one-two' -node.default['hal8000_xmpp']['kredits']['gitea_repo_blacklist'] = 'kosmos/test-one-two' -node.default['hal8000_xmpp']['kredits']['web_url'] = 'https://kredits.kosmos.org' diff --git a/site-cookbooks/kosmos-hubot/metadata.rb b/site-cookbooks/kosmos-hubot/metadata.rb index f3be70f..ea897eb 100644 --- a/site-cookbooks/kosmos-hubot/metadata.rb +++ b/site-cookbooks/kosmos-hubot/metadata.rb @@ -12,4 +12,4 @@ depends 'kosmos-ipfs' depends 'kosmos_openresty' depends 'firewall' depends 'git' -depends 'redisio' +depends 'kosmos_redis' diff --git a/site-cookbooks/kosmos-hubot/recipes/_nodejs.rb b/site-cookbooks/kosmos-hubot/recipes/_nodejs.rb index 366e3ae..c7db821 100644 --- a/site-cookbooks/kosmos-hubot/recipes/_nodejs.rb +++ b/site-cookbooks/kosmos-hubot/recipes/_nodejs.rb @@ -3,5 +3,5 @@ # Recipe:: _nodejs # -node.override["kosmos_nodejs"]["version"] = "16.20.2" +node.override["kosmos_nodejs"]["version"] = "22.23.4" include_recipe "kosmos-nodejs" diff --git a/site-cookbooks/kosmos-hubot/recipes/botka_irc-libera-chat.rb b/site-cookbooks/kosmos-hubot/recipes/botka_irc-libera-chat.rb deleted file mode 100644 index a7340d4..0000000 --- a/site-cookbooks/kosmos-hubot/recipes/botka_irc-libera-chat.rb +++ /dev/null @@ -1,111 +0,0 @@ -# -# Cookbook Name:: kosmos-hubot -# Recipe:: botka_irc-libera-chat -# - -app_name = "botka_irc-libera-chat" -app_path = "/opt/#{app_name}" -app_user = "hubot" -app_group = "hubot" - -build_essential app_name do - compile_time true -end - -include_recipe 'redisio::default' -include_recipe 'redisio::enable' -include_recipe "kosmos-hubot::_user" -include_recipe "kosmos-hubot::_nodejs" -include_recipe "kosmos-base::firewall" - -credentials = Chef::EncryptedDataBagItem.load('credentials', app_name) - -git app_path do - user app_user - group app_group - repository "https://gitea.kosmos.org/kosmos/botka.git" - revision "master" - notifies :restart, "systemd_unit[#{app_name}.service]", :delayed -end - -file "#{app_path}/external-scripts.json" do - mode "0640" - owner app_user - group app_group - content [ - "hubot-help", - "hubot-redis-brain", - "hubot-remotestorage-logger", - "hubot-web-push-notifications", - ].to_json -end - -execute "npm install" do - cwd app_path - environment "HOME" => app_path - user app_user -end - -service_env = { - "HUBOT_LOG_LEVEL" => node.chef_environment == "development" ? "debug" : "info", - # Node 16 bundles its own CA store, which still trusts the compromised - # eMudhra intermediate's root; use the (patched) system trust store instead. - "NODE_OPTIONS" => "--use-openssl-ca", - "HUBOT_IRC_USESSL" => "true", - "HUBOT_IRC_SERVER" => credentials["znc_host"], - "HUBOT_IRC_PORT" => credentials["znc_port"], - "HUBOT_IRC_NICK" => "botka", - "HUBOT_IRC_USERNAME" => credentials['znc_user'], - "HUBOT_IRC_PASSWORD" => credentials['znc_password'], - "HUBOT_IRC_REALNAME" => "botka (kosmos)", - "HUBOT_IRC_ROOMS" => "#kosmos,#kosmos-dev,#kosmos-random,#remotestorage,#hackerbeach,#unhosted,#sockethub,#mastodon", - "HUBOT_IRC_UNFLOOD" => "100", - "HUBOT_RSS_PRINTSUMMARY" => "false", - "HUBOT_RSS_PRINTERROR" => "false", - "HUBOT_RSS_IRCCOLORS" => "true", - "REDIS_URL" => "redis://localhost:6379/botka", - "EXPRESS_PORT" => node[app_name]['http_port'], - "HUBOT_AUTH_ADMIN" => "bkero,raucao", - "HUBOT_HELP_REPLY_IN_PRIVATE" => "true", - "RS_LOGGER_USER" => "kosmos@5apps.com", - "RS_LOGGER_TOKEN" => credentials['rs_logger_token'], - "RS_LOGGER_SERVER_NAME" => "irc.libera.chat", - "RS_LOGGER_PUBLIC" => "true", - "GCM_API_KEY" => credentials['gcm_api_key'], - "VAPID_SUBJECT" => "https://kosmos.org", - "VAPID_PUBLIC_KEY" => credentials['vapid_public_key'], - "VAPID_PRIVATE_KEY" => credentials['vapid_private_key'] -} - -systemd_unit "#{app_name}.service" do - content({ - Unit: { - Description: app_name, - Requires: "redis@6379.service", - After: "redis@6379.service" - }, - - Service: { - ExecStart: "#{app_path}/bin/hubot -a irc", - WorkingDirectory: app_path, - User: app_user, - Group: app_group, - Environment: service_env.map { |k, v| "'#{k}=#{v}'" }, - Restart: 'always' - }, - - Install: { - WantedBy: 'multi-user.target' - } - }) - verify false - triggers_reload true - action [:create, :enable, :start] -end - -firewall_rule app_name do - port node[app_name]['http_port'] - source "10.1.1.0/24" - protocol :tcp - command :allow -end diff --git a/site-cookbooks/kosmos-hubot/recipes/default.rb b/site-cookbooks/kosmos-hubot/recipes/default.rb new file mode 100644 index 0000000..afc986f --- /dev/null +++ b/site-cookbooks/kosmos-hubot/recipes/default.rb @@ -0,0 +1,36 @@ +# +# Cookbook:: kosmos-hubot +# Recipe:: default +# + +build_essential 'hubot' +include_recipe 'git' +include_recipe 'kosmos-base::firewall' +include_recipe 'kosmos-hubot::_user' +include_recipe 'kosmos-hubot::_nodejs' + +# A local Redis provides the brain store for the bots. It is only reachable from +# the node itself, and the bots connect without auth. +node.override['kosmos_redis']['requirepass'] = false +include_recipe 'kosmos_redis::default' + +node['kosmos-hubot']['bots'].each do |app_name, config| + # A bot's extras recipe (if any) augments its config before it is declared. + include_recipe "kosmos-hubot::#{config['extras']}" if config['extras'] + + bot = node['kosmos-hubot']['bots'][app_name] + + kosmos_hubot_bot app_name do + repository bot['repository'] + revision bot['revision'] || 'master' + adapter bot['adapter'] + robot_name bot['robot_name'] + http_port bot['http_port'] + redis bot['redis'] || false + firewall bot['firewall'] || {} + external_scripts bot['external_scripts'] || [] + env bot['env'] || {} + secret_env bot['secret_env'] || {} + extra_files bot['extra_files'] || [] + end +end diff --git a/site-cookbooks/kosmos-hubot/recipes/hal8000_extras.rb b/site-cookbooks/kosmos-hubot/recipes/hal8000_extras.rb new file mode 100644 index 0000000..0fff9c0 --- /dev/null +++ b/site-cookbooks/kosmos-hubot/recipes/hal8000_extras.rb @@ -0,0 +1,48 @@ +# +# Cookbook:: kosmos-hubot +# Recipe:: hal8000_extras +# +# Augments the hal8000 bot with its kredits-specific configuration. Included by +# the default recipe before the bot is declared; it only sets node overrides +# (no resources of its own). +# + +app_name = 'hal8000_xmpp' +secrets = data_bag_item('credentials', 'hubot')[app_name] + +# The kredits integration targets the current IPFS gateway and RSK testnet node. +ipfs_host = search(:node, 'role:ipfs_gateway').first&.dig('knife_zero', 'host') +rsk_host = search(:node, 'role:rskj_testnet').first&.dig('knife_zero', 'host') + +if ipfs_host + node.override['kosmos-hubot']['bots'][app_name]['env']['IPFS_API_HOST'] = ipfs_host +end +if rsk_host + node.override['kosmos-hubot']['bots'][app_name]['env']['KREDITS_PROVIDER_URL'] = "http://#{rsk_host}:4444" +end + +env = node['kosmos-hubot']['bots'][app_name]['env'] + +# @kredits/hubot-kredits reads these from a .env file in its package directory; +# it and the Ethereum wallet are written by the bot resource after npm install. +node.override['kosmos-hubot']['bots'][app_name]['extra_files'] = [ + { + 'path' => 'node_modules/@kredits/hubot-kredits/.env', + 'mode' => '0600', + 'content' => <<-EOF +GITEA_TOKEN=#{secrets['gitea_token']} +GITHUB_TOKEN=#{secrets['github_token']} +KREDITS_PROVIDER_URL=#{env['KREDITS_PROVIDER_URL']} +IPFS_API_HOST=#{env['IPFS_API_HOST']} +IPFS_API_PORT=#{env['IPFS_API_PORT']} +IPFS_API_PROTOCOL=#{env['IPFS_API_PROTOCOL']} +KREDITS_WALLET_PATH=../../#{env['KREDITS_WALLET_PATH']} +KREDITS_WALLET_PASSWORD=#{secrets['kredits_wallet_password']} + EOF + }, + { + 'path' => 'wallet.json', + 'source' => 'wallet.json', + 'mode' => '0644' + } +] diff --git a/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb b/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb deleted file mode 100644 index 6c8b2d7..0000000 --- a/site-cookbooks/kosmos-hubot/recipes/hal8000_xmpp.rb +++ /dev/null @@ -1,140 +0,0 @@ -# -# Cookbook Name:: kosmos-hubot -# Recipe:: hal8000_xmpp -# - -app_name = "hal8000_xmpp" -app_path = "/opt/#{app_name}" -app_user = "hubot" -app_group = "hubot" - -build_essential app_name do - compile_time true -end - -include_recipe 'redisio::default' -include_recipe 'redisio::enable' -include_recipe "kosmos-hubot::_user" -include_recipe "kosmos-hubot::_nodejs" -include_recipe "kosmos-base::firewall" - -data_bag = Chef::EncryptedDataBagItem.load('credentials', app_name) - -git app_path do - user app_user - group app_group - repository "https://gitea.kosmos.org/kosmos/hal8000.git" - revision "master" - notifies :restart, "systemd_unit[#{app_name}.service]", :delayed -end - -file "#{app_path}/external-scripts.json" do - mode "0640" - owner app_user - group app_group - content node[app_name]['hubot_scripts'].to_json -end - -execute "npm install" do - cwd app_path - environment "HOME" => app_path - user app_user -end - -ipfs_host = search(:node, "role:ipfs_gateway").first["knife_zero"]["host"] -node.override[app_name]['kredits']['ipfs_host'] = ipfs_host - -rsk_node_ip = search(:node, "role:rskj_testnet").first["knife_zero"]["host"] -node.override[app_name]['kredits']['provider_url'] = "http://#{rsk_node_ip}:4444" - -file "#{app_path}/node_modules/@kredits/hubot-kredits/.env" do - mode "0600" - owner app_user - group app_group - content <<-EOF -GITEA_TOKEN=#{data_bag['gitea_token']} -GITHUB_TOKEN=#{data_bag['github_token']} -KREDITS_PROVIDER_URL=#{node[app_name]['kredits']['provider_url']} -IPFS_API_HOST=#{node[app_name]['kredits']['ipfs_host']} -IPFS_API_PORT=#{node[app_name]['kredits']['ipfs_port']} -IPFS_API_PROTOCOL=#{node[app_name]['kredits']['ipfs_protocol']} -KREDITS_WALLET_PATH=../../#{node[app_name]['kredits']['wallet_path']} -KREDITS_WALLET_PASSWORD=#{data_bag['kredits_wallet_password']} - EOF -end - -cookbook_file "#{app_path}/wallet.json" do - source "wallet.json" -end - -service_env = { - "HUBOT_LOG_LEVEL" => node.chef_environment == "development" ? "debug" : "info", - # Node 16 bundles its own CA store, which still trusts the compromised - # eMudhra intermediate's root; use the (patched) system trust store instead. - "NODE_OPTIONS" => "--use-openssl-ca", - "HUBOT_XMPP_USERNAME" => "hal8000@kosmos.org/hubot", - "HUBOT_XMPP_PASSWORD" => data_bag['xmpp_password'], - "HUBOT_XMPP_ROOMS" => node[app_name]['rooms'].join(','), - "HUBOT_AUTH_ADMIN" => node[app_name]['auth_admins'].join(','), - "HUBOT_RSS_PRINTSUMMARY" => "false", - "HUBOT_RSS_PRINTERROR" => "false", - "HUBOT_RSS_IRCCOLORS" => "true", - "HUBOT_PLUSPLUS_POINTS_TERM" => "karma,karma", - "HUBOT_RSS_HEADER" => "Update:", - "HUBOT_HELP_REPLY_IN_PRIVATE" => "true", - "REDIS_URL" => "redis://localhost:6379/#{app_name}", - "EXPRESS_PORT" => node[app_name]['http_port'], - "WEBHOOK_TOKEN" => data_bag['webhook_token'], - "IPFS_API_HOST" => node[app_name]['kredits']['ipfs_host'], - "IPFS_API_PORT" => node[app_name]['kredits']['ipfs_port'], - "IPFS_API_PROTOCOL" => node[app_name]['kredits']['ipfs_protocol'], - "KREDITS_WEB_URL" => node[app_name]['kredits']['web_url'], - "KREDITS_ROOM" => node[app_name]['kredits']['room'], - "KREDITS_WEBHOOK_TOKEN" => data_bag['kredits_webhook_token'], - "KREDITS_PROVIDER_URL" => node[app_name]['kredits']['provider_url'], - "KREDITS_WALLET_PATH" => node[app_name]['kredits']['wallet_path'], - "KREDITS_WALLET_PASSWORD" => data_bag['kredits_wallet_password'], - "KREDITS_MEDIAWIKI_URL" => node[app_name]['kredits']['mediawiki_url'], - "KREDITS_GITHUB_REPO_BLACKLIST" => node[app_name]['kredits']['github_repo_blacklist'], - "KREDITS_GITEA_REPO_BLACKLIST" => node[app_name]['kredits']['gitea_repo_blacklist'], - "KREDITS_GRANT_HOST" => node[app_name]['domain'], - "KREDITS_GRANT_PROTOCOL" => "https", - "KREDITS_SESSION_SECRET" => data_bag['kredits_session_secret'], - "KREDITS_GITHUB_KEY" => data_bag['kredits_github_key'], - "KREDITS_GITHUB_SECRET" => data_bag['kredits_github_secret'], - "KREDITS_ZOOM_JWT" => data_bag['kredits_zoom_jwt'], - "KREDITS_ZOOM_MEETING_WHITELIST" => "414901303,82557072771" -} - -systemd_unit "#{app_name}.service" do - content({ - Unit: { - Description: app_name, - Requires: "redis@6379.service", - After: "redis@6379.service" - }, - - Service: { - ExecStart: "#{app_path}/bin/hubot -a xmpp --name hal8000", - WorkingDirectory: app_path, - User: app_user, - Group: app_group, - Environment: service_env.map { |k, v| "'#{k}=#{v}'" }, - Restart: 'always' - }, - - Install: { - WantedBy: 'multi-user.target' - } - }) - verify false - triggers_reload true - action [:create, :enable, :start] -end - -firewall_rule app_name do - port node[app_name]['http_port'] - source "10.1.1.0/24" - protocol :tcp - command :allow -end diff --git a/site-cookbooks/kosmos-hubot/recipes/nginx.rb b/site-cookbooks/kosmos-hubot/recipes/nginx.rb new file mode 100644 index 0000000..0afc8ae --- /dev/null +++ b/site-cookbooks/kosmos-hubot/recipes/nginx.rb @@ -0,0 +1,26 @@ +# +# Cookbook:: kosmos-hubot +# Recipe:: nginx +# +# Fronts the bots' HTTP endpoints (webhooks, health) via openresty. +# + +node['kosmos-hubot']['vhosts'].each do |vhost| + domain = vhost['domain'] + upstream_host = search(:node, 'role:hubot').first['knife_zero']['host'] + + tls_cert_for domain do + auth 'gandi_dns' + acme_domain 'letsencrypt.kosmos.org' + action :create + end + + openresty_site domain do + template 'nginx_conf_hubot.erb' + variables express_port: vhost['port'], + server_name: domain, + ssl_cert: "/etc/letsencrypt/live/#{domain}/fullchain.pem", + ssl_key: "/etc/letsencrypt/live/#{domain}/privkey.pem", + upstream_host: upstream_host + end +end diff --git a/site-cookbooks/kosmos-hubot/recipes/nginx_botka_irc-libera-chat.rb b/site-cookbooks/kosmos-hubot/recipes/nginx_botka_irc-libera-chat.rb deleted file mode 100644 index 3d028c2..0000000 --- a/site-cookbooks/kosmos-hubot/recipes/nginx_botka_irc-libera-chat.rb +++ /dev/null @@ -1,18 +0,0 @@ -domain = "irc-libera-chat.botka.kosmos.chat" - -upstream_host = search(:node, "role:hubot").first["knife_zero"]["host"] - -tls_cert_for domain do - auth "gandi_dns" - acme_domain "letsencrypt.kosmos.org" - action :create -end - -openresty_site domain do - template 'nginx_conf_hubot.erb' - variables express_port: node['botka_irc-libera-chat']['http_port'], - server_name: domain, - ssl_cert: "/etc/letsencrypt/live/#{domain}/fullchain.pem", - ssl_key: "/etc/letsencrypt/live/#{domain}/privkey.pem", - upstream_host: upstream_host -end diff --git a/site-cookbooks/kosmos-hubot/recipes/nginx_hal8000_xmpp.rb b/site-cookbooks/kosmos-hubot/recipes/nginx_hal8000_xmpp.rb deleted file mode 100644 index 072760e..0000000 --- a/site-cookbooks/kosmos-hubot/recipes/nginx_hal8000_xmpp.rb +++ /dev/null @@ -1,19 +0,0 @@ -app_name = "hal8000_xmpp" -domain = node[app_name]['domain'] - -upstream_host = search(:node, "role:hubot").first["knife_zero"]["host"] - -tls_cert_for domain do - auth "gandi_dns" - acme_domain "letsencrypt.kosmos.org" - action :create -end - -openresty_site domain do - template 'nginx_conf_hubot.erb' - variables express_port: node[app_name]['http_port'], - server_name: node[app_name]['domain'], - ssl_cert: "/etc/letsencrypt/live/#{node[app_name]['domain']}/fullchain.pem", - ssl_key: "/etc/letsencrypt/live/#{node[app_name]['domain']}/privkey.pem", - upstream_host: upstream_host -end diff --git a/site-cookbooks/kosmos-hubot/resources/bot.rb b/site-cookbooks/kosmos-hubot/resources/bot.rb new file mode 100644 index 0000000..b1318db --- /dev/null +++ b/site-cookbooks/kosmos-hubot/resources/bot.rb @@ -0,0 +1,131 @@ +# +# Cookbook:: kosmos-hubot +# Resource:: bot +# +# Sets up a single hubot: git checkout, dependencies, generated +# external-scripts.json, and a hardened systemd unit. +# + +resource_name :kosmos_hubot_bot +provides :kosmos_hubot_bot + +property :app_name, String, name_property: true +property :repository, String, required: true +property :revision, String, default: 'master' +property :adapter, String, required: true +property :robot_name, String, required: true +property :http_port, Integer +property :external_scripts, Array, default: [] +property :env, Hash, default: {} +property :secret_env, Hash, default: {} +property :redis, [true, false], default: false +property :firewall, Hash, default: {} +property :extra_files, Array, default: [] + +default_action :create + +action :create do + app_name = new_resource.app_name + app_path = "/opt/#{app_name}" + app_user = 'hubot' + app_group = 'hubot' + + secrets = data_bag_item('credentials', 'hubot')[app_name] || {} + + service_env = { + 'HUBOT_LOG_LEVEL' => node.chef_environment == 'development' ? 'debug' : 'info', + # Node bundles its own CA store, which still trusts the compromised eMudhra + # intermediate's root; use the (patched) system trust store instead. + 'NODE_OPTIONS' => '--use-openssl-ca' + } + service_env['EXPRESS_PORT'] = new_resource.http_port.to_s if new_resource.http_port + service_env.merge!(new_resource.env) + new_resource.secret_env.each do |env_key, bag_key| + service_env[env_key] = secrets[bag_key] + end + + git app_path do + user app_user + group app_group + repository new_resource.repository + revision new_resource.revision + notifies :restart, "systemd_unit[#{app_name}.service]", :delayed + end + + execute "npm install (#{app_name})" do + cwd app_path + user app_user + group app_group + environment 'HOME' => app_path + end + + file "#{app_path}/external-scripts.json" do + mode '0640' + owner app_user + group app_group + content new_resource.external_scripts.to_json + end + + new_resource.extra_files.each do |f| + if f['source'] + cookbook_file "#{app_path}/#{f['path']}" do + source f['source'] + mode f['mode'] || '0644' + owner app_user + group app_group + end + else + file "#{app_path}/#{f['path']}" do + content f['content'] + mode f['mode'] || '0644' + owner app_user + group app_group + end + end + end + + unit = { + Unit: { + Description: app_name + }, + Service: { + Type: 'simple', + User: app_user, + Group: app_group, + WorkingDirectory: app_path, + ExecStart: "#{app_path}/bin/hubot -a #{new_resource.adapter} --name #{new_resource.robot_name}", + Environment: service_env.map { |k, v| "'#{k}=#{v}'" }, + Restart: 'always', + RestartSec: '2', + TimeoutSec: '10', + PrivateTmp: true, + ProtectSystem: 'full', + NoNewPrivileges: true, + PrivateDevices: true + }, + Install: { + WantedBy: 'multi-user.target' + } + } + + if new_resource.redis + unit[:Unit][:Requires] = 'redis@6379.service' + unit[:Unit][:After] = 'redis@6379.service' + end + + systemd_unit "#{app_name}.service" do + content unit + verify false + triggers_reload true + action [:create, :enable, :start] + end + + if new_resource.firewall && !new_resource.firewall.empty? + firewall_rule app_name do + port new_resource.firewall['port'] + source new_resource.firewall['source'] + protocol new_resource.firewall.fetch('protocol', :tcp) + command :allow + end + end +end From d3062d701f4066f79e4895fde998da595b448afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 28 Oct 2026 14:35:09 +0400 Subject: [PATCH 12/18] hubot: drop the unused hubot-auth script hubot-auth is CoffeeScript-only (needs porting) and nothing in the bots calls robot.auth; remove it from hal8000's external scripts and drop HUBOT_AUTH_ADMIN from both bots. --- site-cookbooks/kosmos-hubot/attributes/default.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/site-cookbooks/kosmos-hubot/attributes/default.rb b/site-cookbooks/kosmos-hubot/attributes/default.rb index 5f696be..6900e5d 100644 --- a/site-cookbooks/kosmos-hubot/attributes/default.rb +++ b/site-cookbooks/kosmos-hubot/attributes/default.rb @@ -33,7 +33,6 @@ default['kosmos-hubot']['bots'] = { 'HUBOT_IRC_REALNAME' => 'botka (kosmos)', 'HUBOT_IRC_ROOMS' => '#kosmos,#kosmos-dev,#kosmos-random,#remotestorage,#hackerbeach,#unhosted,#sockethub,#mastodon', 'HUBOT_IRC_UNFLOOD' => '100', - 'HUBOT_AUTH_ADMIN' => 'bkero,raucao', 'HUBOT_HELP_REPLY_IN_PRIVATE' => 'true', 'REDIS_URL' => 'redis://localhost:6379?botka', 'RS_LOGGER_USER' => 'kosmos@5apps.com', @@ -53,7 +52,7 @@ default['kosmos-hubot']['bots'] = { 'firewall' => { 'port' => 8082, 'source' => '10.1.1.0/24' }, 'external_scripts' => %w[ hubot-help hubot-redis-brain hubot-rules hubot-incoming-webhook - hubot-auth @kredits/hubot-kredits + @kredits/hubot-kredits ], 'secret_env' => { 'HUBOT_XMPP_PASSWORD' => 'xmpp_password', @@ -68,7 +67,6 @@ default['kosmos-hubot']['bots'] = { 'env' => { 'HUBOT_XMPP_USERNAME' => 'hal8000@kosmos.org/hubot', 'HUBOT_XMPP_ROOMS' => 'kosmos@kosmos.chat,kosmos-dev@kosmos.chat,kredits@kosmos.chat,info@kosmos.chat,ops@kosmos.chat', - 'HUBOT_AUTH_ADMIN' => '', 'HUBOT_HELP_REPLY_IN_PRIVATE' => 'true', 'HUBOT_RSS_PRINTSUMMARY' => 'false', 'HUBOT_RSS_PRINTERROR' => 'false', From 086b31a387350c70e364f2e73ca9a7c017cb87a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 28 Oct 2026 17:07:03 +0400 Subject: [PATCH 13/18] kosmos-hubot: declare bot firewall rules at the recipe top level The per-bot firewall_rule declared inside the kosmos_hubot_bot custom resource was not collected by the ufw provider when it rebuilt the rules file, so the bots' HTTP ports were never opened. Declare the rules in the default recipe instead, matching the pattern used by the other cookbooks, and drop the now-unused firewall property from the resource. --- site-cookbooks/kosmos-hubot/recipes/default.rb | 16 +++++++++++++++- site-cookbooks/kosmos-hubot/resources/bot.rb | 17 +++++++---------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/site-cookbooks/kosmos-hubot/recipes/default.rb b/site-cookbooks/kosmos-hubot/recipes/default.rb index afc986f..1562c91 100644 --- a/site-cookbooks/kosmos-hubot/recipes/default.rb +++ b/site-cookbooks/kosmos-hubot/recipes/default.rb @@ -9,6 +9,21 @@ include_recipe 'kosmos-base::firewall' include_recipe 'kosmos-hubot::_user' include_recipe 'kosmos-hubot::_nodejs' +# Expose each bot's HTTP port (webhooks, health) to the proxy network. Declared +# at recipe top level so the ufw provider collects them when it rebuilds the +# firewall rules file. +node['kosmos-hubot']['bots'].each do |app_name, config| + firewall = config['firewall'] || {} + next if firewall.empty? + + firewall_rule "hubot_#{app_name}" do + port firewall['port'] + source firewall['source'] + protocol firewall.fetch('protocol', :tcp) + command :allow + end +end + # A local Redis provides the brain store for the bots. It is only reachable from # the node itself, and the bots connect without auth. node.override['kosmos_redis']['requirepass'] = false @@ -27,7 +42,6 @@ node['kosmos-hubot']['bots'].each do |app_name, config| robot_name bot['robot_name'] http_port bot['http_port'] redis bot['redis'] || false - firewall bot['firewall'] || {} external_scripts bot['external_scripts'] || [] env bot['env'] || {} secret_env bot['secret_env'] || {} diff --git a/site-cookbooks/kosmos-hubot/resources/bot.rb b/site-cookbooks/kosmos-hubot/resources/bot.rb index b1318db..ca665b3 100644 --- a/site-cookbooks/kosmos-hubot/resources/bot.rb +++ b/site-cookbooks/kosmos-hubot/resources/bot.rb @@ -19,7 +19,6 @@ property :external_scripts, Array, default: [] property :env, Hash, default: {} property :secret_env, Hash, default: {} property :redis, [true, false], default: false -property :firewall, Hash, default: {} property :extra_files, Array, default: [] default_action :create @@ -44,6 +43,12 @@ action :create do service_env[env_key] = secrets[bag_key] end + directory app_path do + owner app_user + group app_group + mode '0755' + end + git app_path do user app_user group app_group @@ -53,6 +58,7 @@ action :create do end execute "npm install (#{app_name})" do + command 'npm install' cwd app_path user app_user group app_group @@ -119,13 +125,4 @@ action :create do triggers_reload true action [:create, :enable, :start] end - - if new_resource.firewall && !new_resource.firewall.empty? - firewall_rule app_name do - port new_resource.firewall['port'] - source new_resource.firewall['source'] - protocol new_resource.firewall.fetch('protocol', :tcp) - command :allow - end - end end From e53df92b577488486ffee1c5e93b0b4a44c37ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 28 Oct 2026 17:07:11 +0400 Subject: [PATCH 14/18] kosmos-hubot: scope the nginx upstream to the production environment search(:node, 'role:hubot') also matched retired nodes outside production (e.g. nodejs-4 in _default), so the vhosts could proxy to a dead host and return 502. Restrict the search to chef_environment:production and pick deterministically. --- site-cookbooks/kosmos-hubot/recipes/nginx.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/site-cookbooks/kosmos-hubot/recipes/nginx.rb b/site-cookbooks/kosmos-hubot/recipes/nginx.rb index 0afc8ae..279ef16 100644 --- a/site-cookbooks/kosmos-hubot/recipes/nginx.rb +++ b/site-cookbooks/kosmos-hubot/recipes/nginx.rb @@ -6,8 +6,16 @@ # node['kosmos-hubot']['vhosts'].each do |vhost| - domain = vhost['domain'] - upstream_host = search(:node, 'role:hubot').first['knife_zero']['host'] + domain = vhost['domain'] + + # Restrict to the production environment and pick deterministically, so a + # stray node carrying role[hubot] (e.g. a retired one in _default) can't + # hijack the upstream. + upstream = search(:node, 'role:hubot AND chef_environment:production') + .sort_by(&:name).first + raise 'No production node with role[hubot] found for the hubot vhosts' unless upstream + + upstream_host = upstream['knife_zero']['host'] tls_cert_for domain do auth 'gandi_dns' From 69d0f72f87f460952d24e2f0024ad701a3c9455f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 28 Oct 2026 17:07:11 +0400 Subject: [PATCH 15/18] kosmos_redis: relax the chef_version constraint Use ">= 18.0" instead of "~> 18.0" so newer Chef/Cinc releases converge. --- site-cookbooks/kosmos_redis/metadata.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site-cookbooks/kosmos_redis/metadata.rb b/site-cookbooks/kosmos_redis/metadata.rb index c5ee33b..a1a68ae 100644 --- a/site-cookbooks/kosmos_redis/metadata.rb +++ b/site-cookbooks/kosmos_redis/metadata.rb @@ -6,7 +6,7 @@ maintainer_email "mail@kosmos.org" license "MIT" description "redisio wrapper cookbook" version "0.2.0" -chef_version "~> 18.0" # apt_repository resource +chef_version ">= 18.0" # apt_repository resource depends "redisio" depends "backup" From 54e838d77ddf701499c1eff73b651db84b652025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 28 Oct 2026 17:07:17 +0400 Subject: [PATCH 16/18] credentials: consolidate the hubot secrets into one data bag Read the hal8000 webhook token from credentials/hubot in the mediawiki and alertmanager recipes, and drop the now-unused botka_irc-libera-chat, hal8000_xmpp and hal8000_freenode bags. --- .../credentials/botka_irc-libera-chat.json | 66 ----------------- data_bags/credentials/hal8000_freenode.json | 31 -------- data_bags/credentials/hal8000_xmpp.json | 73 ------------------- data_bags/credentials/hubot.json | 17 +++++ .../kosmos-mediawiki/recipes/default.rb | 3 +- .../kosmos_prometheus/recipes/alertmanager.rb | 4 +- 6 files changed, 20 insertions(+), 174 deletions(-) delete mode 100644 data_bags/credentials/botka_irc-libera-chat.json delete mode 100644 data_bags/credentials/hal8000_freenode.json delete mode 100644 data_bags/credentials/hal8000_xmpp.json create mode 100644 data_bags/credentials/hubot.json diff --git a/data_bags/credentials/botka_irc-libera-chat.json b/data_bags/credentials/botka_irc-libera-chat.json deleted file mode 100644 index f1676c4..0000000 --- a/data_bags/credentials/botka_irc-libera-chat.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "id": "botka_irc-libera-chat", - "rs_logger_token": { - "encrypted_data": "K0totr/aUIZ/ArLHLsXOCtpmhYRyebJv5GOsYEgCo3s2ObdYOWsDEIkhj7ho\nf9fFwdii\n", - "iv": "zpdygbdrCqumn2Us\n", - "auth_tag": "xqjaUt8BjBPNNKas47gzmA==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "nickserv_password": { - "encrypted_data": "QBKx59optTSTfhLAtZ1h0dcQD1Xh7/ornslRb4en1A==\n", - "iv": "DrpHZPcfac7Cq4wf\n", - "auth_tag": "CUI9Wf/EObL3CpEr8MfqLw==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "znc_host": { - "encrypted_data": "U7n5uu39L6ShcnLY3iKuRsUZpI4NuZvBnBkqedc=\n", - "iv": "QFqfqQEjxmi1O12z\n", - "auth_tag": "uLXLy7AGt1/SarBx8QpWSw==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "znc_port": { - "encrypted_data": "T2lZ6mXjUwlulpyqkM1VTBGf2zU/SBi+\n", - "iv": "BtF1HBLST3RmYk/s\n", - "auth_tag": "htf0Clo/9H5FWbTwFc4W8Q==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "znc_user": { - "encrypted_data": "q/rGDx8G51/MlFr/fE29GyZmEC7e9OnSJK07yCABNw==\n", - "iv": "Lvj2yLTpWba1cOLT\n", - "auth_tag": "V43rrkVYDXmZ7csTKiQpJg==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "znc_password": { - "encrypted_data": "0QZxtYSDiYEfXGm3rMsAmE2lUWa1P5X0L1d7jHIdPww=\n", - "iv": "+yICaeYVn1HtaVYS\n", - "auth_tag": "7qe/85sww2TCc31/l0Gszw==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "gcm_api_key": { - "encrypted_data": "UnarSxLUWt9JUUxRH7yeeA1VKOvePwwCCPO1Vi0F6hl/1P8HOxpGp2O1a/1R\nC78KVFfi9vmLa2Gwlw==\n", - "iv": "C25lCAZeEiki0Ruq\n", - "auth_tag": "XhtL3J79PpltMLs6Y2xg9A==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "vapid_public_key": { - "encrypted_data": "LU7E8wwKSrJLzsM1yDl6MO/SiVQ600NWpLhZFO+cb5jfHGoU4x0Zvo9zKVle\nG+SGzvm+OM14ld2MTbHCCtSkHuIR+tO2lrDg19gnQRmHRrf4A8Vbhl7nQ33E\ntwKjo2UUTSYQGGeNbSAOHw==\n", - "iv": "ppveJZXb2+nFHOwP\n", - "auth_tag": "ZcY9O5NCenlWrp4zS9uSjQ==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "vapid_private_key": { - "encrypted_data": "69aGVRJOA4MjErS5uB4HG4Y4vw4ch6t6xe0AFN1Ir5a6uC7JULy5pTWpht/k\n5KWSJgynjYH405bIompkTMo=\n", - "iv": "zofy6sRjLVO2q/XD\n", - "auth_tag": "f79xkUJpaoYhcCLqA7WURQ==\n", - "version": 3, - "cipher": "aes-256-gcm" - } -} \ No newline at end of file diff --git a/data_bags/credentials/hal8000_freenode.json b/data_bags/credentials/hal8000_freenode.json deleted file mode 100644 index 4e09a1a..0000000 --- a/data_bags/credentials/hal8000_freenode.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "id": "hal8000_freenode", - "nickserv_password": { - "encrypted_data": "rkCsvjS6EipHlxgxPdSiPVl6CCyjyy845P2ftSykmIW0+fxahTSOxbSMYJl8\n1DW6Go88ZE+eKKWIugp2nWDS+5Pnx58I\n", - "iv": "EvNcR0eqpZngoNJx\n", - "auth_tag": "kKFPUuff8llgVZYROTg/EA==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "webhook_token": { - "encrypted_data": "ItDsU9w6HCGS7ykQdkZEXQEZzPEt6bW42Fbh00AtZz+h7JmQ\n", - "iv": "OdaAg/XoUMIEfQEQ\n", - "auth_tag": "9ThqnVhWEZbo4jF4lqa5TA==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "kredits_webhook_token": { - "encrypted_data": "kUp4XAQkwWFphQT1f4wsGVJJtmhBqrEiW6W1D1ONrpZ0z94=\n", - "iv": "XiGtQlKn4BvAeaS1\n", - "auth_tag": "1hkTI7ccxBN4/6U4VF19WQ==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "kredits_wallet_password": { - "encrypted_data": "mKcJBPto0OdPpBXB5x3ynxq01DA2CEz476lTAgjGjTNDHQ==\n", - "iv": "LIvTZ+fx1suOcnjD\n", - "auth_tag": "mcjLU242nqtNn5XR7ku4BQ==\n", - "version": 3, - "cipher": "aes-256-gcm" - } -} \ No newline at end of file diff --git a/data_bags/credentials/hal8000_xmpp.json b/data_bags/credentials/hal8000_xmpp.json deleted file mode 100644 index bf88cf3..0000000 --- a/data_bags/credentials/hal8000_xmpp.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "id": "hal8000_xmpp", - "xmpp_password": { - "encrypted_data": "fFHM54i7mlfD9dpZWJWIsjSXGzekctbsNZc0DOSOijg=\n", - "iv": "kKkZ9dbk3l9Gygkz\n", - "auth_tag": "Sx2oYaWlrrjn27x/7+Hciw==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "webhook_token": { - "encrypted_data": "I1b52yjcijL8G/5rQOaK+Us4mqj3zfrS14C1FNX/QGYfk5qo\n", - "iv": "JKhm4WyvV09cW5WY\n", - "auth_tag": "EdbJQ+robm4Ekibbid8WQg==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "kredits_webhook_token": { - "encrypted_data": "xybuwYnGSyOZd+C89mkXYQNp3QOdGCNby7T26VHQw9cJ9BI=\n", - "iv": "p4JRoucq6402kJTA\n", - "auth_tag": "Q9tRvbk9Yeii7BAqB153PA==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "kredits_wallet_password": { - "encrypted_data": "GiWqyYSRipI7+HTV2mlaWZBchPkDSAqf0c0GwmcGXzuc6g==\n", - "iv": "txQNno2CBfscTEZ9\n", - "auth_tag": "NUEYUOOYIczFW11T8RPApg==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "kredits_session_secret": { - "encrypted_data": "BZoUrStW49IHALMNBMZFQwu40VeuUF++JRI7MqggXhSCWu9l5MIuBjwiOd9O\nUXaAfY3NgrYC\n", - "iv": "yGLVq+owGBs7UHle\n", - "auth_tag": "ar81Cg0/ae83pNqkTos+Zg==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "gitea_token": { - "encrypted_data": "8cN3+ptBOv4Ha0VBrq94fXnuLSYUWU3WaRXcblt4KSak3GMziYCTVw2mYxbE\nsfrYH7Ukypht/mEu+kY=\n", - "iv": "Vz7f+khl5VNdbtaG\n", - "auth_tag": "OXWUsIUcazdSfQIlevpKPg==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "github_token": { - "encrypted_data": "13MyNGNRKiKIrfwauhV/XS7rIzaflHL+BARDO/JMMUxoG8vsNvci5gHBHr95\nr9hqRu218czoZFl/O/Q=\n", - "iv": "QuC8lFzZwH73J4Dy\n", - "auth_tag": "ZZdBCh/K3l8i1QKpT8aR1A==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "kredits_github_key": { - "encrypted_data": "5iAJpKHyGIpNnB7xo03p150Gs4chTlx5cWBXXeo8QVzho5aBOhjc\n", - "iv": "6X1u9Y0SPPZrS26f\n", - "auth_tag": "KMB9VG/M3emW6Cd9kX23Zg==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "kredits_github_secret": { - "encrypted_data": "bY2Aa7h3gJ8owtK9HZ0Fnvi35HtvAKX25R1WF1Lgmqj8tKIASnDjTHTh38b2\nfa8NFt4OAwrOkIDK6sM=\n", - "iv": "quCBdq6e5Hs3O5Yr\n", - "auth_tag": "jOAcMTyqSJ04H8ZPVZz6Zw==\n", - "version": 3, - "cipher": "aes-256-gcm" - }, - "kredits_zoom_jwt": { - "encrypted_data": "d/UWwRN99Tel0oHBwrXptDzQ8k2GpWers8+aD4bfVqM4aoHex7+4/IQE529i\n2wm//3yxzPfVicqnYvToWDorKvL7buZdQmTJNRdN7gxjt+NLNtB4FYWImNnI\ntfyufpx3bYsA1ULLfoO14QDm/nfvf8jsftArwXZZMLEbCHrS7E4LYS9+UVQM\nRytRTgQhCtB9hPSUwykZ3OgYrJnm5HCPss+IHaTDmPG5jY8xZs4xvUdeFa6T\nkzsfpXf5aIzxei5UEl75mTm3XXAARvc=\n", - "iv": "6WaZCHRuUp46xTAG\n", - "auth_tag": "ZA2gb03AO/GGq8Kl+You5Q==\n", - "version": 3, - "cipher": "aes-256-gcm" - } -} \ No newline at end of file diff --git a/data_bags/credentials/hubot.json b/data_bags/credentials/hubot.json new file mode 100644 index 0000000..3e54fa5 --- /dev/null +++ b/data_bags/credentials/hubot.json @@ -0,0 +1,17 @@ +{ + "id": "hubot", + "botka_irc-libera-chat": { + "encrypted_data": "ceX2xHYBYFTuTEdHSXCgfONEy+uj0iRkqVoJXTCz/V/LP5Wvyp8sPF0YNBjC\nX+DwClNtAfKevXV1XKompL4X9/vTVW4Z4gLGhkwalslj8AcjMYwNgdee+d4F\n2alckEuuTnVW5Qd4PYp2m8mwbXRrSY8XvZ5A7hrCdw9kQPFS/O1jWlqcCVn1\ntLMK4dpKcgsO0eVjqq3sBPv1QEYuE1U4T3yHV0fSc1kpT+Ta9VDpU/PyMBQ7\nNj+ehGbsZ4Auzigb2/qVVecdR1YzCg+9re5FJ9jS\n", + "iv": "NWpKmpywB552avSP\n", + "auth_tag": "mbgE/xkW1XumbvteRav+Ug==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "hal8000_xmpp": { + "encrypted_data": "l5OXEJXWopStN9T3CZaaZuXdpuuT13Lpd3srIrOBg17K8x4P2qcc+24iPpC2\n3zemKbh2FEcdmguQIIqDbcqJ5kEFfRZz65K1YKFAgiWVrNToyZ6TUzgy1rE2\nIdljvuaVjN900WdU+2AjqD6LAEzAuV/LoLDV88h7EfHbyp2IjwB/BJYpOGaG\ntdO8hvTTvlqpwLokshm7U/BntKqOUK6K+/gq0mBvqqotEtEe3d+Wsvxn+MxR\n2eFuSaX2FCgDLk+aQNAOqN1+D9JnmIsp8AzNaftAULo3GTnQZThVF9giPCfq\nDhN3IRDoyvYkSE+Yg2QuX/4P8TDFIL+x9B26T6o5qQs+9Bzo85K1VJpH4QsZ\nG89D2QIf6R2IoNAzQJUcbpp4HhIXLcZxMbDMUF9tA13SVkOMjtyvaZciEMfT\n1uxKXOU9kGQLoiB9kSAFQVzZP6zxufUbK7FeqYMpU3bHl6062wn90EMeLPto\nsLV+7Lw3LKK6AjlKl8kg8aUdSsB38rQ5CAOyqFAAf1eWMO1cpxCpYxzvNXd6\n80A85SKVkYT88pK1HwZYxqAdTkeImtZIxMgvirr8ywRKRutzcZi3Z2XHvfF0\nep/vnJY+VJuhZ4+k1QxcY9flLw+OVbu1+eVMdZlznHmfkr9sTrRIwNFTq4c+\n53ZCFH3Fa3DoW8O4nzkAcOEt+Fr94f3bCJemuvtkqF5F3ahNImjyTubXF9El\nqWXub+NvEyqiW+XtO1BlRfocgNLty6uqtXH6vPWZmzXsf+J4vZkBbmhhjgkq\nw67FAw43ikXZSzQRus1yBBGfuiNX9VITp0gW5v4uINuqYTWY5F0/i4ySjmbU\niiJLSvsklV4TVehW99o/987Ex70lqlGdnjxp0aot3lYR26EiBYA=\n", + "iv": "hUs2EqXGGFJeu9pv\n", + "auth_tag": "n298dNAMe8K/N6/KMEoQQg==\n", + "version": 3, + "cipher": "aes-256-gcm" + } +} \ No newline at end of file diff --git a/site-cookbooks/kosmos-mediawiki/recipes/default.rb b/site-cookbooks/kosmos-mediawiki/recipes/default.rb index 2ed9194..3da5f35 100644 --- a/site-cookbooks/kosmos-mediawiki/recipes/default.rb +++ b/site-cookbooks/kosmos-mediawiki/recipes/default.rb @@ -100,8 +100,7 @@ ark "MediawikiHubot" do action :cherry_pick end -hubot_credentials = data_bag_item('credentials', 'hal8000_xmpp') -webhook_token = hubot_credentials['webhook_token'] +webhook_token = data_bag_item('credentials', 'hubot')['hal8000_xmpp']['webhook_token'] template "#{node['mediawiki']['webdir']}/extensions/MediawikiHubot/DefaultConfig.php" do source "MediawikiHubot/DefaultConfig.php.erb" diff --git a/site-cookbooks/kosmos_prometheus/recipes/alertmanager.rb b/site-cookbooks/kosmos_prometheus/recipes/alertmanager.rb index b8625a9..156d6a4 100644 --- a/site-cookbooks/kosmos_prometheus/recipes/alertmanager.rb +++ b/site-cookbooks/kosmos_prometheus/recipes/alertmanager.rb @@ -58,8 +58,8 @@ file "/usr/local/bin/alertmanager" do end # TODO use Auth header once supported by hubot-incoming-webhook -credentials = data_bag_item("credentials", "hal8000_xmpp") -webhook_url = "https://hal8000.kosmos.chat/incoming/#{credentials['webhook_token']}" +webhook_token = data_bag_item('credentials', 'hubot')['hal8000_xmpp']['webhook_token'] +webhook_url = "https://hal8000.kosmos.chat/incoming/#{webhook_token}" template "/etc/prometheus/alertmanager.yml" do source "alertmanager.yml.erb" From 34db4dee8f79a0a0cfa6c05e0d8d4451009e1eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 28 Oct 2026 17:07:22 +0400 Subject: [PATCH 17/18] Retire nodejs-4; add role[hubot] to nodejs-5 Drop the nodejs-4 node object (host is powered off) and give nodejs-5 role[hubot], so hubot runs on Node 22 there. --- nodes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes b/nodes index afffa40..ee04075 160000 --- a/nodes +++ b/nodes @@ -1 +1 @@ -Subproject commit afffa4003116baee16fe86539d372c5c63d62e4f +Subproject commit ee04075fbc5efecf0c199d79164b413beb321cfd From 0405459db03c9a1e0a0623f77b2cf8d4e611c38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 28 Oct 2026 18:14:38 +0400 Subject: [PATCH 18/18] Document remaining items for hubot migration --- doc/hubot-migration.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 doc/hubot-migration.md diff --git a/doc/hubot-migration.md b/doc/hubot-migration.md new file mode 100644 index 0000000..9f51a42 --- /dev/null +++ b/doc/hubot-migration.md @@ -0,0 +1,30 @@ +# Hubot migration — waiting on + +Deferred/blocked items from moving botka (IRC) and hal8000 (XMPP) off the +retired `nodejs-4` onto `nodejs-5`. + +## Upstream: @xmpp/stream-management crash + +`ackQueue()` dereferences `undefined` when the server acknowledges more stanzas +than are still queued, so hal8000 logs +`TypeError: Cannot read properties of undefined (reading 'stanza')` on every +(re)connect. Caught by the client, harmless. + +Fix is upstream: https://github.com/xmppjs/xmpp.js/pull/1119 (guard the empty +queue). `kosmos/hubot-xmpp` logs the error stack trace meanwhile. + +## Upstream: xmpp.js CI is red + +The prosody e2e step fails to load `/certs/localhost.key` (environmental +regression); `main` fails on every Node version. Unrelated to our changes, but +it blocks PR CI there. + +## Upstream: xmpp.js jest testMatch regression + +`jest.config.cjs` sets `testMatch: ["**/test/*.js"]`, dropping +`...defaults.testMatch`, so package-root `*.test.js` files are no longer run. +#1119 works around it by moving one file. + +## Deferred features + +- botka as an XMPP bot