From 938d8e6294bcb18f49909153314eb2cfac755f63 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sat, 22 Nov 2025 16:58:14 +0100 Subject: [PATCH 01/49] Make open source the default, and check for sass instead --- Dockerfile | 2 +- Dockerfile.dev | 2 +- Gemfile | 4 +--- Gemfile.lock | 14 ++------------ bin/rails | 6 ------ config/application.rb | 2 ++ config/ci.rb | 3 +-- config/initializers/authentication.rb | 2 -- config/routes.rb | 3 ++- lib/bootstrap.rb | 5 ----- lib/fizzy.rb | 5 +++++ test/controllers/sessions_controller_test.rb | 2 +- test/test_helper.rb | 4 ---- 13 files changed, 16 insertions(+), 38 deletions(-) delete mode 100644 config/initializers/authentication.rb delete mode 100644 lib/bootstrap.rb create mode 100644 lib/fizzy.rb diff --git a/Dockerfile b/Dockerfile index 6341f3fc19..3f272de4e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ RUN apt-get update -qq && \ # Install application gems COPY Gemfile Gemfile.lock .ruby-version ./ -COPY lib/bootstrap.rb ./lib/bootstrap.rb +COPY lib/fizzy.rb ./lib/fizzy.rb COPY gems ./gems/ RUN --mount=type=secret,id=GITHUB_TOKEN --mount=type=cache,id=fizzy-permabundle-${RUBY_VERSION},sharing=locked,target=/permabundle \ gem install bundler && \ diff --git a/Dockerfile.dev b/Dockerfile.dev index 75b284570a..9c20fcda5d 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -18,7 +18,7 @@ RUN apt-get update -qq && \ # Install application gems COPY Gemfile Gemfile.lock .ruby-version ./ -COPY lib/bootstrap.rb ./lib/bootstrap.rb +COPY lib/fizzy.rb ./lib/fizzy.rb COPY gems ./gems/ RUN --mount=type=secret,id=GITHUB_TOKEN --mount=type=cache,id=fizzy-devbundle-${RUBY_VERSION},sharing=locked,target=/devbundle \ gem install bundler foreman && \ diff --git a/Gemfile b/Gemfile index d008f49073..bdd047171a 100644 --- a/Gemfile +++ b/Gemfile @@ -76,8 +76,6 @@ group :test do gem "mocha" end -require_relative "lib/bootstrap" -unless Bootstrap.oss_config? - eval_gemfile "gems/fizzy-saas/Gemfile" +group :saas do gem "fizzy-saas", path: "gems/fizzy-saas" end diff --git a/Gemfile.lock b/Gemfile.lock index f8001333cc..6b7771fd34 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,13 +1,3 @@ -GIT - remote: https://github.com/basecamp/queenbee-plugin - revision: eb01c697de1ad028afc65cc7d9b5345a7a8e849f - ref: eb01c697de1ad028afc65cc7d9b5345a7a8e849f - specs: - queenbee (3.2.0) - activeresource - builder - rexml - GIT remote: https://github.com/basecamp/rails-structured-logging revision: 76960cb5c15fc2b6b5f7542e05d7dcc031cef9e6 @@ -387,6 +377,8 @@ GEM public_suffix (7.0.0) puma (7.1.0) nio4r (~> 2.0) + queenbee (1.5.1) + json (> 1.8) raabro (1.4.0) racc (1.8.1) rack (3.2.4) @@ -585,7 +577,6 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES - activeresource autotuner aws-sdk-s3 bcrypt (~> 3.1.7) @@ -612,7 +603,6 @@ DEPENDENCIES prometheus-client-mmap (~> 1.3) propshaft puma (>= 5.0) - queenbee! rack-mini-profiler rails! rails_structured_logging! diff --git a/bin/rails b/bin/rails index ab22dd7709..0739660237 100755 --- a/bin/rails +++ b/bin/rails @@ -1,10 +1,4 @@ #!/usr/bin/env ruby -require_relative "../lib/bootstrap" -if !Bootstrap.oss_config? - # default from rails/test_unit/runner.rb but adding the saas gem test files - ENV["DEFAULT_TEST"] = "{gems/fizzy-saas/,}test/**/*_test.rb" - ENV["DEFAULT_TEST_EXCLUDE"] = "{gems/fizzy-saas/,}test/{system,dummy,fixtures}/**/*_test.rb" -end APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands' diff --git a/config/application.rb b/config/application.rb index 27177a5cb9..0399caacac 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,5 +1,7 @@ require_relative "boot" require "rails/all" +require_relative "../lib/fizzy" + Bundler.require(*Rails.groups) module Fizzy diff --git a/config/ci.rb b/config/ci.rb index be81c58c96..689060927b 100644 --- a/config/ci.rb +++ b/config/ci.rb @@ -9,8 +9,7 @@ step "Security: Importmap audit", "bin/importmap audit" step "Security: Brakeman audit", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error" - step "Tests: Rails: SaaS config", "bin/rails test" - step "Tests: Rails: OSS config", "OSS_CONFIG=1 bin/rails test" + step "Tests: Rails", "bin/rails test" step "Tests: System", "bin/rails test:system" if success? diff --git a/config/initializers/authentication.rb b/config/initializers/authentication.rb deleted file mode 100644 index 5556a9661e..0000000000 --- a/config/initializers/authentication.rb +++ /dev/null @@ -1,2 +0,0 @@ -require "bootstrap" -Rails.application.config.x.oss_config = Bootstrap.oss_config? diff --git a/config/routes.rb b/config/routes.rb index 73acfa75ed..87fe32b7a3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -224,7 +224,8 @@ get "manifest" => "rails/pwa#manifest", as: :pwa_manifest get "service-worker" => "pwa#service_worker" - unless Rails.application.config.x.oss_config + # TODO: Can we move this just to the engine + if Fizzy.saas? mount Fizzy::Saas::Engine, at: "/", as: "saas" end diff --git a/lib/bootstrap.rb b/lib/bootstrap.rb deleted file mode 100644 index 6d52f3f49e..0000000000 --- a/lib/bootstrap.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Bootstrap - def self.oss_config? - ENV.fetch("OSS_CONFIG", "") != "" || !File.directory?(File.expand_path("../gems/fizzy-saas", __dir__)) - end -end diff --git a/lib/fizzy.rb b/lib/fizzy.rb new file mode 100644 index 0000000000..2a3b6e2211 --- /dev/null +++ b/lib/fizzy.rb @@ -0,0 +1,5 @@ +module Fizzy + def self.saas? + defined?(Fizzy::Saas::Engine) + end +end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 623423fdf0..7fed629008 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -21,7 +21,7 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest end end - unless Bootstrap.oss_config? + if Fizzy.saas? test "create for a new user" do untenanted do assert_difference -> { Identity.count }, +1 do diff --git a/test/test_helper.rb b/test/test_helper.rb index bad1fdb2a1..722d39d59f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -152,7 +152,3 @@ def uuid_v7_with_timestamp(time, seed_string) ActiveSupport.on_load(:active_record_fixture_set) do prepend(FixturesTestHelper) end - -unless Rails.application.config.x.oss_config - load File.expand_path("../gems/fizzy-saas/test/test_helper.rb", __dir__) -end From eb24fc9d716135d1d7263bccbf7e998920e3f8e5 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sat, 22 Nov 2025 19:08:23 +0100 Subject: [PATCH 02/49] Make sass property depend on txt file or env var --- Gemfile | 5 ++++- Gemfile.lock | 11 ----------- lib/fizzy.rb | 3 ++- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Gemfile b/Gemfile index bdd047171a..65b7be293f 100644 --- a/Gemfile +++ b/Gemfile @@ -76,6 +76,9 @@ group :test do gem "mocha" end -group :saas do +require_relative "lib/fizzy" +if Fizzy.saas? + gem "activeresource", require: "active_resource" + gem "queenbee", git: "https://github.com/basecamp/queenbee-plugin" gem "fizzy-saas", path: "gems/fizzy-saas" end diff --git a/Gemfile.lock b/Gemfile.lock index 6b7771fd34..e2c03955c8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -122,14 +122,6 @@ GIT tsort (>= 0.2) zeitwerk (~> 2.6) -PATH - remote: gems/fizzy-saas - specs: - fizzy-saas (0.1.0) - queenbee - rails (>= 8.1.0.beta1) - rails_structured_logging - GEM remote: https://rubygems.org/ specs: @@ -377,8 +369,6 @@ GEM public_suffix (7.0.0) puma (7.1.0) nio4r (~> 2.0) - queenbee (1.5.1) - json (> 1.8) raabro (1.4.0) racc (1.8.1) rack (3.2.4) @@ -587,7 +577,6 @@ DEPENDENCIES capybara debug faker - fizzy-saas! geared_pagination (~> 1.2) image_processing (~> 1.14) importmap-rails diff --git a/lib/fizzy.rb b/lib/fizzy.rb index 2a3b6e2211..7bda122529 100644 --- a/lib/fizzy.rb +++ b/lib/fizzy.rb @@ -1,5 +1,6 @@ module Fizzy def self.saas? - defined?(Fizzy::Saas::Engine) + return @saas if defined?(@saas) + @saas = !!(ENV["SAAS"] || File.exist?(File.expand_path("../../tmp/saas.txt", __dir__))) end end From 3f9300f369693882dce32dc4522aa682df9705e0 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 04:32:22 +0100 Subject: [PATCH 03/49] Move db check to the Fizzy module --- config/database.yml | 9 +++------ lib/fizzy.rb | 27 ++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/config/database.yml b/config/database.yml index 913ba7cba0..2a9a3aa2e6 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,7 +1,4 @@ <% - database_adapter = ENV.fetch("DATABASE_ADAPTER", "mysql") - use_sqlite = database_adapter == "sqlite" - if ENV["MIGRATE"].present? mysql_app_user_key = "MYSQL_ALTER_USER" mysql_app_password_key = "MYSQL_ALTER_PASSWORD" @@ -17,7 +14,7 @@ %> default: &default - <% if use_sqlite %> + <% if Fizzy.db_adapter.sqlite? %> adapter: sqlite3 pool: 5 timeout: 5000 @@ -33,7 +30,7 @@ default: &default <% end %> development: - <% if use_sqlite %> + <% if Fizzy.db_adapter.sqlite? %> primary: <<: *default database: storage/development.sqlite3 @@ -69,7 +66,7 @@ development: # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - <% if use_sqlite %> + <% if Fizzy.db_adapter.sqlite? %> primary: <<: *default database: storage/test.sqlite3 diff --git a/lib/fizzy.rb b/lib/fizzy.rb index 7bda122529..16a1ca3a46 100644 --- a/lib/fizzy.rb +++ b/lib/fizzy.rb @@ -1,6 +1,27 @@ module Fizzy - def self.saas? - return @saas if defined?(@saas) - @saas = !!(ENV["SAAS"] || File.exist?(File.expand_path("../../tmp/saas.txt", __dir__))) + class << self + def saas? + return @saas if defined?(@saas) + @saas = !!(ENV["SAAS"] || File.exist?(File.expand_path("../../tmp/saas.txt", __dir__))) + end + + def db_adapter + @db_adapter ||= DbAdapter.new ENV.fetch("DATABASE_ADAPTER", saas? ? "mysql" : "sqlite") + end + end + + class DbAdapter + def initialize(name) + @name = name.to_s + end + + def to_s + @name + end + + # Not using inquiry so that it works before Rails env loads. + def sqlite? + @name == "sqlite" + end end end From 272e95740017316099cf67f265feca9ab394b4cd Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 05:15:52 +0100 Subject: [PATCH 04/49] Split database files --- config/database.mysql.yml | 100 ++++++++++++++++++++++++++++++ config/database.sqlite.yml | 21 +++++++ config/database.yml | 124 +------------------------------------ 3 files changed, 122 insertions(+), 123 deletions(-) create mode 100644 config/database.mysql.yml create mode 100644 config/database.sqlite.yml diff --git a/config/database.mysql.yml b/config/database.mysql.yml new file mode 100644 index 0000000000..3c68fd0810 --- /dev/null +++ b/config/database.mysql.yml @@ -0,0 +1,100 @@ +<% + if ENV["MIGRATE"].present? + mysql_app_user_key = "MYSQL_ALTER_USER" + mysql_app_password_key = "MYSQL_ALTER_PASSWORD" + else + mysql_app_user_key = "MYSQL_APP_USER" + mysql_app_password_key = "MYSQL_APP_PASSWORD" + end + + mysql_app_user = ENV[mysql_app_user_key] + mysql_app_password = ENV[mysql_app_password_key] +%> + +default: &default + adapter: trilogy + host: <%= ENV.fetch "FIZZY_DB_HOST", "127.0.0.1" %> + port: <%= ENV.fetch "FIZZY_DB_PORT", 3306 %> + pool: 50 + timeout: 5000 + variables: + transaction_isolation: READ-COMMITTED + +development: + primary: + <<: *default + database: fizzy_development + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + replica: + <<: *default + database: fizzy_development + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + replica: true + cable: + <<: *default + database: development_cable + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: db/cable_migrate + cache: + <<: *default + database: development_cache + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: db/cache_migrate + queue: + <<: *default + database: development_queue + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: db/queue_migrate + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + primary: + <<: *default + database: fizzy_test + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + replica: + <<: *default + database: fizzy_test + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + replica: true + +production: &production + primary: + <<: *default + database: fizzy_production + host: <%= ENV["MYSQL_DATABASE_HOST"] %> + username: <%= mysql_app_user %> + password: <%= mysql_app_password %> + replica: + <<: *default + database: fizzy_production + host: <%= ENV["MYSQL_DATABASE_REPLICA_HOST"] %> + username: <%= ENV["MYSQL_READONLY_USER"] %> + password: <%= ENV["MYSQL_READONLY_PASSWORD"] %> + replica: true + cable: + <<: *default + database: fizzy_solidcable_production + host: <%= ENV["MYSQL_SOLID_CABLE_HOST"] %> + username: <%= mysql_app_user %> + password: <%= mysql_app_password %> + migrations_paths: db/cable_migrate + queue: + <<: *default + database: fizzy_solidqueue_production + host: <%= ENV["MYSQL_SOLID_QUEUE_HOST"] %> + username: <%= mysql_app_user %> + password: <%= mysql_app_password %> + migrations_paths: db/queue_migrate + cache: + <<: *default + database: fizzy_solidcache_production + host: <%= ENV["MYSQL_SOLID_CACHE_HOST"] %> + username: <%= mysql_app_user %> + password: <%= mysql_app_password %> + migrations_paths: db/cache_migrate + +beta: *production +staging: *production diff --git a/config/database.sqlite.yml b/config/database.sqlite.yml new file mode 100644 index 0000000000..44fcda8cc9 --- /dev/null +++ b/config/database.sqlite.yml @@ -0,0 +1,21 @@ +default: &default + adapter: sqlite3 + pool: 50 + timeout: 5000 + +development: + primary: + <<: *default + database: db/development.sqlite3 + schema_dump: schema_sqlite.rb + +test: + primary: + <<: *default + database: db/test.sqlite3 + schema_dump: schema_sqlite.rb + +production: + primary: + <<: *default + database: db/production.sqlite3 diff --git a/config/database.yml b/config/database.yml index 2a9a3aa2e6..84663579d2 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,123 +1 @@ -<% - if ENV["MIGRATE"].present? - mysql_app_user_key = "MYSQL_ALTER_USER" - mysql_app_password_key = "MYSQL_ALTER_PASSWORD" - max_execution_time_ms = 0 # No limit - else - mysql_app_user_key = "MYSQL_APP_USER" - mysql_app_password_key = "MYSQL_APP_PASSWORD" - max_execution_time_ms = 5_000 - end - - mysql_app_user = ENV[mysql_app_user_key] - mysql_app_password = ENV[mysql_app_password_key] -%> - -default: &default - <% if Fizzy.db_adapter.sqlite? %> - adapter: sqlite3 - pool: 5 - timeout: 5000 - <% else %> - adapter: trilogy - host: <%= ENV.fetch "FIZZY_DB_HOST", "127.0.0.1" %> - port: <%= ENV.fetch "FIZZY_DB_PORT", 3306 %> - pool: 50 - timeout: 5000 - variables: - transaction_isolation: READ-COMMITTED - max_execution_time: <%= max_execution_time_ms %> - <% end %> - -development: - <% if Fizzy.db_adapter.sqlite? %> - primary: - <<: *default - database: storage/development.sqlite3 - schema_dump: schema_sqlite.rb - <% else %> - primary: - <<: *default - database: fizzy_development - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: - <<: *default - database: fizzy_development - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: true - cable: - <<: *default - database: development_cable - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/cable_migrate - cache: - <<: *default - database: development_cache - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/cache_migrate - queue: - <<: *default - database: development_queue - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/queue_migrate - <% end %> - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - <% if Fizzy.db_adapter.sqlite? %> - primary: - <<: *default - database: storage/test.sqlite3 - schema_dump: schema_sqlite.rb - <% else %> - primary: - <<: *default - database: fizzy_test - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: - <<: *default - database: fizzy_test - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: true - <% end %> - -production: &production - primary: - <<: *default - database: fizzy_production - host: <%= ENV["MYSQL_DATABASE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - replica: - <<: *default - database: fizzy_production - host: <%= ENV["MYSQL_DATABASE_REPLICA_HOST"] %> - username: <%= ENV["MYSQL_READONLY_USER"] %> - password: <%= ENV["MYSQL_READONLY_PASSWORD"] %> - replica: true - cable: - <<: *default - database: fizzy_solidcable_production - host: <%= ENV["MYSQL_SOLID_CABLE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/cable_migrate - queue: - <<: *default - database: fizzy_solidqueue_production - host: <%= ENV["MYSQL_SOLID_QUEUE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/queue_migrate - cache: - <<: *default - database: fizzy_solidcache_production - host: <%= ENV["MYSQL_SOLID_CACHE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/cache_migrate - -beta: *production -staging: *production +<%= ERB.new(File.read(File.join(__dir__, "database.#{Fizzy.db_adapter}.yml"))).result %> From 61c15c46251238fffbe2034c2ccf4c5e24aba071 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 05:16:22 +0100 Subject: [PATCH 05/49] Use bundler groups instead of conditional So that the gemfile.lock does not change across invocations --- Gemfile | 3 +-- Gemfile.lock | 20 ++++++++++++++++++++ bin/rails | 17 ++++++++++++++--- config/application.rb | 4 +++- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 65b7be293f..5a5f86f78f 100644 --- a/Gemfile +++ b/Gemfile @@ -76,8 +76,7 @@ group :test do gem "mocha" end -require_relative "lib/fizzy" -if Fizzy.saas? +group :saas, optional: true do gem "activeresource", require: "active_resource" gem "queenbee", git: "https://github.com/basecamp/queenbee-plugin" gem "fizzy-saas", path: "gems/fizzy-saas" diff --git a/Gemfile.lock b/Gemfile.lock index e2c03955c8..6e8bbd96b5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/basecamp/queenbee-plugin + revision: 15faf03a876c5e66b67753d2e1ddb24f1eb5abb2 + specs: + queenbee (3.2.0) + activeresource + builder + rexml + GIT remote: https://github.com/basecamp/rails-structured-logging revision: 76960cb5c15fc2b6b5f7542e05d7dcc031cef9e6 @@ -122,6 +131,14 @@ GIT tsort (>= 0.2) zeitwerk (~> 2.6) +PATH + remote: gems/fizzy-saas + specs: + fizzy-saas (0.1.0) + queenbee + rails (>= 8.1.0.beta1) + rails_structured_logging + GEM remote: https://rubygems.org/ specs: @@ -567,6 +584,7 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES + activeresource autotuner aws-sdk-s3 bcrypt (~> 3.1.7) @@ -577,6 +595,7 @@ DEPENDENCIES capybara debug faker + fizzy-saas! geared_pagination (~> 1.2) image_processing (~> 1.14) importmap-rails @@ -592,6 +611,7 @@ DEPENDENCIES prometheus-client-mmap (~> 1.3) propshaft puma (>= 5.0) + queenbee! rack-mini-profiler rails! rails_structured_logging! diff --git a/bin/rails b/bin/rails index 0739660237..36ee7f7124 100755 --- a/bin/rails +++ b/bin/rails @@ -1,4 +1,15 @@ #!/usr/bin/env ruby -APP_PATH = File.expand_path('../config/application', __dir__) -require_relative '../config/boot' -require 'rails/commands' +APP_PATH = File.expand_path("../config/application", __dir__) + +require_relative "../lib/fizzy" + +if Fizzy.saas? + ENV["BUNDLE_WITH"] = [ ENV["BUNDLE_WITH"] , "saas" ].compact.join(",") +end + +require_relative "../config/boot" + +puts "SaaS version (#{Fizzy.db_adapter})" if Fizzy.saas? + +require "rails/commands" + diff --git a/config/application.rb b/config/application.rb index 0399caacac..4bfbab4a22 100644 --- a/config/application.rb +++ b/config/application.rb @@ -2,7 +2,9 @@ require "rails/all" require_relative "../lib/fizzy" -Bundler.require(*Rails.groups) +groups = Rails.groups +groups << :saas if Fizzy.saas? +Bundler.require(*groups) module Fizzy class Application < Rails::Application From 69f046da6af8ee7a34a73262e3659043e1ed026c Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 05:27:40 +0100 Subject: [PATCH 06/49] Move the SASS database config to the gem --- config/database.mysql.yml | 102 +++------------------------- config/database.yml | 12 +++- gems/fizzy-saas/config/database.yml | 100 +++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 92 deletions(-) create mode 100644 gems/fizzy-saas/config/database.yml diff --git a/config/database.mysql.yml b/config/database.mysql.yml index 3c68fd0810..e3188b1acb 100644 --- a/config/database.mysql.yml +++ b/config/database.mysql.yml @@ -1,100 +1,20 @@ -<% - if ENV["MIGRATE"].present? - mysql_app_user_key = "MYSQL_ALTER_USER" - mysql_app_password_key = "MYSQL_ALTER_PASSWORD" - else - mysql_app_user_key = "MYSQL_APP_USER" - mysql_app_password_key = "MYSQL_APP_PASSWORD" - end - - mysql_app_user = ENV[mysql_app_user_key] - mysql_app_password = ENV[mysql_app_password_key] -%> - default: &default adapter: trilogy - host: <%= ENV.fetch "FIZZY_DB_HOST", "127.0.0.1" %> - port: <%= ENV.fetch "FIZZY_DB_PORT", 3306 %> + host: <%= ENV.fetch("MYSQL_HOST", "127.0.0.1") %> + port: <%= ENV.fetch("MYSQL_PORT", "3306") %> + username: <%= ENV.fetch("MYSQL_USER", "root") %> + password: <%= ENV["MYSQL_PASSWORD"] %> pool: 50 timeout: 5000 - variables: - transaction_isolation: READ-COMMITTED development: - primary: - <<: *default - database: fizzy_development - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: - <<: *default - database: fizzy_development - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: true - cable: - <<: *default - database: development_cable - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/cable_migrate - cache: - <<: *default - database: development_cache - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/cache_migrate - queue: - <<: *default - database: development_queue - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/queue_migrate + <<: *default + database: fizzy_development -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. test: - primary: - <<: *default - database: fizzy_test - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: - <<: *default - database: fizzy_test - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: true - -production: &production - primary: - <<: *default - database: fizzy_production - host: <%= ENV["MYSQL_DATABASE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - replica: - <<: *default - database: fizzy_production - host: <%= ENV["MYSQL_DATABASE_REPLICA_HOST"] %> - username: <%= ENV["MYSQL_READONLY_USER"] %> - password: <%= ENV["MYSQL_READONLY_PASSWORD"] %> - replica: true - cable: - <<: *default - database: fizzy_solidcable_production - host: <%= ENV["MYSQL_SOLID_CABLE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/cable_migrate - queue: - <<: *default - database: fizzy_solidqueue_production - host: <%= ENV["MYSQL_SOLID_QUEUE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/queue_migrate - cache: - <<: *default - database: fizzy_solidcache_production - host: <%= ENV["MYSQL_SOLID_CACHE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/cache_migrate + <<: *default + database: fizzy_test -beta: *production -staging: *production +production: + <<: *default + database: fizzy_production diff --git a/config/database.yml b/config/database.yml index 84663579d2..0229af3e39 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1 +1,11 @@ -<%= ERB.new(File.read(File.join(__dir__, "database.#{Fizzy.db_adapter}.yml"))).result %> +<% + require_relative "../lib/fizzy" + + config_path = if Fizzy.saas? + gem_path = Gem::Specification.find_by_name("fizzy-saas").gem_dir + File.join(gem_path, "config", "database.yml") + else + File.join(__dir__, "database.#{Fizzy.db_adapter}.yml") + end +%> +<%= ERB.new(File.read(config_path)).result %> diff --git a/gems/fizzy-saas/config/database.yml b/gems/fizzy-saas/config/database.yml new file mode 100644 index 0000000000..3c68fd0810 --- /dev/null +++ b/gems/fizzy-saas/config/database.yml @@ -0,0 +1,100 @@ +<% + if ENV["MIGRATE"].present? + mysql_app_user_key = "MYSQL_ALTER_USER" + mysql_app_password_key = "MYSQL_ALTER_PASSWORD" + else + mysql_app_user_key = "MYSQL_APP_USER" + mysql_app_password_key = "MYSQL_APP_PASSWORD" + end + + mysql_app_user = ENV[mysql_app_user_key] + mysql_app_password = ENV[mysql_app_password_key] +%> + +default: &default + adapter: trilogy + host: <%= ENV.fetch "FIZZY_DB_HOST", "127.0.0.1" %> + port: <%= ENV.fetch "FIZZY_DB_PORT", 3306 %> + pool: 50 + timeout: 5000 + variables: + transaction_isolation: READ-COMMITTED + +development: + primary: + <<: *default + database: fizzy_development + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + replica: + <<: *default + database: fizzy_development + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + replica: true + cable: + <<: *default + database: development_cable + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: db/cable_migrate + cache: + <<: *default + database: development_cache + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: db/cache_migrate + queue: + <<: *default + database: development_queue + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + migrations_paths: db/queue_migrate + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + primary: + <<: *default + database: fizzy_test + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + replica: + <<: *default + database: fizzy_test + port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> + replica: true + +production: &production + primary: + <<: *default + database: fizzy_production + host: <%= ENV["MYSQL_DATABASE_HOST"] %> + username: <%= mysql_app_user %> + password: <%= mysql_app_password %> + replica: + <<: *default + database: fizzy_production + host: <%= ENV["MYSQL_DATABASE_REPLICA_HOST"] %> + username: <%= ENV["MYSQL_READONLY_USER"] %> + password: <%= ENV["MYSQL_READONLY_PASSWORD"] %> + replica: true + cable: + <<: *default + database: fizzy_solidcable_production + host: <%= ENV["MYSQL_SOLID_CABLE_HOST"] %> + username: <%= mysql_app_user %> + password: <%= mysql_app_password %> + migrations_paths: db/cable_migrate + queue: + <<: *default + database: fizzy_solidqueue_production + host: <%= ENV["MYSQL_SOLID_QUEUE_HOST"] %> + username: <%= mysql_app_user %> + password: <%= mysql_app_password %> + migrations_paths: db/queue_migrate + cache: + <<: *default + database: fizzy_solidcache_production + host: <%= ENV["MYSQL_SOLID_CACHE_HOST"] %> + username: <%= mysql_app_user %> + password: <%= mysql_app_password %> + migrations_paths: db/cache_migrate + +beta: *production +staging: *production From 4cbeacb9c8e993b6c08f6280421cef3179f64b08 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 05:45:58 +0100 Subject: [PATCH 07/49] Move kamal deploy config to the gem --- bin/kamal | 16 ++++++++++++++++ .../fizzy-saas/config}/deploy.beta.yml | 0 gems/fizzy-saas/config/deploy.dhh.yml | 12 ++++++++++++ .../fizzy-saas/config}/deploy.production.yml | 0 .../fizzy-saas/config}/deploy.staging.yml | 0 {config => gems/fizzy-saas/config}/deploy.yml | 0 6 files changed, 28 insertions(+) rename {config => gems/fizzy-saas/config}/deploy.beta.yml (100%) create mode 100644 gems/fizzy-saas/config/deploy.dhh.yml rename {config => gems/fizzy-saas/config}/deploy.production.yml (100%) rename {config => gems/fizzy-saas/config}/deploy.staging.yml (100%) rename {config => gems/fizzy-saas/config}/deploy.yml (100%) diff --git a/bin/kamal b/bin/kamal index cbe59b95ed..5c8ea6be73 100755 --- a/bin/kamal +++ b/bin/kamal @@ -22,6 +22,22 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this end require "rubygems" + +require_relative "../lib/fizzy" +if Fizzy.saas? + ENV["BUNDLE_WITH"] = [ ENV["BUNDLE_WITH"], "saas" ].compact.join(",") +end + require "bundler/setup" +if Fizzy.saas? + gem_path = Gem::Specification.find_by_name("fizzy-saas").gem_dir + deploy_config = File.join(gem_path, "config", "deploy.yml") + + # Add -c option to ARGV if not already present + unless ARGV.include?("-c") || ARGV.include?("--config-file") + ARGV.unshift("-c", deploy_config) + end +end + load Gem.bin_path("kamal", "kamal") diff --git a/config/deploy.beta.yml b/gems/fizzy-saas/config/deploy.beta.yml similarity index 100% rename from config/deploy.beta.yml rename to gems/fizzy-saas/config/deploy.beta.yml diff --git a/gems/fizzy-saas/config/deploy.dhh.yml b/gems/fizzy-saas/config/deploy.dhh.yml new file mode 100644 index 0000000000..05bf874d41 --- /dev/null +++ b/gems/fizzy-saas/config/deploy.dhh.yml @@ -0,0 +1,12 @@ +servers: + web: + hosts: + - test-1 + jobs: + hosts: + - test-1 +env: + clear: + ARTENANT: test + +proxy: false diff --git a/config/deploy.production.yml b/gems/fizzy-saas/config/deploy.production.yml similarity index 100% rename from config/deploy.production.yml rename to gems/fizzy-saas/config/deploy.production.yml diff --git a/config/deploy.staging.yml b/gems/fizzy-saas/config/deploy.staging.yml similarity index 100% rename from config/deploy.staging.yml rename to gems/fizzy-saas/config/deploy.staging.yml diff --git a/config/deploy.yml b/gems/fizzy-saas/config/deploy.yml similarity index 100% rename from config/deploy.yml rename to gems/fizzy-saas/config/deploy.yml From 5032cbae28cb35fef985567fa75de853b71d459d Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 05:49:03 +0100 Subject: [PATCH 08/49] Use kamal from the --- bin/kamal | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/kamal b/bin/kamal index 5c8ea6be73..4cdcdc588d 100755 --- a/bin/kamal +++ b/bin/kamal @@ -34,9 +34,12 @@ if Fizzy.saas? gem_path = Gem::Specification.find_by_name("fizzy-saas").gem_dir deploy_config = File.join(gem_path, "config", "deploy.yml") - # Add -c option to ARGV if not already present unless ARGV.include?("-c") || ARGV.include?("--config-file") - ARGV.unshift("-c", deploy_config) + if ARGV.empty? || ARGV.first.start_with?("-") + ARGV.unshift("-c", deploy_config) + else + ARGV.insert(1, "-c", deploy_config) + end end end From f7a01b1b0ade7242f47d0ca2af2ac03ffc7afbc2 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 05:54:49 +0100 Subject: [PATCH 09/49] Extract common method to configure bundle --- bin/kamal | 4 +--- bin/rails | 5 +---- lib/fizzy.rb | 6 ++++++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bin/kamal b/bin/kamal index 4cdcdc588d..862f9036a6 100755 --- a/bin/kamal +++ b/bin/kamal @@ -24,9 +24,7 @@ end require "rubygems" require_relative "../lib/fizzy" -if Fizzy.saas? - ENV["BUNDLE_WITH"] = [ ENV["BUNDLE_WITH"], "saas" ].compact.join(",") -end +Fizzy.configure_bundle require "bundler/setup" diff --git a/bin/rails b/bin/rails index 36ee7f7124..e644775f8f 100755 --- a/bin/rails +++ b/bin/rails @@ -2,10 +2,7 @@ APP_PATH = File.expand_path("../config/application", __dir__) require_relative "../lib/fizzy" - -if Fizzy.saas? - ENV["BUNDLE_WITH"] = [ ENV["BUNDLE_WITH"] , "saas" ].compact.join(",") -end +Fizzy.configure_bundle require_relative "../config/boot" diff --git a/lib/fizzy.rb b/lib/fizzy.rb index 16a1ca3a46..0a87dd8154 100644 --- a/lib/fizzy.rb +++ b/lib/fizzy.rb @@ -8,6 +8,12 @@ def saas? def db_adapter @db_adapter ||= DbAdapter.new ENV.fetch("DATABASE_ADAPTER", saas? ? "mysql" : "sqlite") end + + def configure_bundle + if saas? + ENV["BUNDLE_WITH"] = [ENV["BUNDLE_WITH"], "saas"].compact.join(",") + end + end end class DbAdapter From 67b373a277c9fcb1118dcbbc71d1f676875c1399 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 06:01:45 +0100 Subject: [PATCH 10/49] Move kamal hooks to the saas gem --- {.kamal => gems/fizzy-saas/.kamal}/hooks/post-deploy | 0 {.kamal => gems/fizzy-saas/.kamal}/hooks/pre-build | 0 {.kamal => gems/fizzy-saas/.kamal}/hooks/pre-connect | 0 gems/fizzy-saas/config/deploy.yml | 1 + 4 files changed, 1 insertion(+) rename {.kamal => gems/fizzy-saas/.kamal}/hooks/post-deploy (100%) rename {.kamal => gems/fizzy-saas/.kamal}/hooks/pre-build (100%) rename {.kamal => gems/fizzy-saas/.kamal}/hooks/pre-connect (100%) diff --git a/.kamal/hooks/post-deploy b/gems/fizzy-saas/.kamal/hooks/post-deploy similarity index 100% rename from .kamal/hooks/post-deploy rename to gems/fizzy-saas/.kamal/hooks/post-deploy diff --git a/.kamal/hooks/pre-build b/gems/fizzy-saas/.kamal/hooks/pre-build similarity index 100% rename from .kamal/hooks/pre-build rename to gems/fizzy-saas/.kamal/hooks/pre-build diff --git a/.kamal/hooks/pre-connect b/gems/fizzy-saas/.kamal/hooks/pre-connect similarity index 100% rename from .kamal/hooks/pre-connect rename to gems/fizzy-saas/.kamal/hooks/pre-connect diff --git a/gems/fizzy-saas/config/deploy.yml b/gems/fizzy-saas/config/deploy.yml index b565e5a504..ea3ca3c49c 100644 --- a/gems/fizzy-saas/config/deploy.yml +++ b/gems/fizzy-saas/config/deploy.yml @@ -1,6 +1,7 @@ service: fizzy image: basecamp/fizzy asset_path: /rails/public/assets +hooks_path: <%= File.join(Gem::Specification.find_by_name("fizzy-saas").gem_dir, ".kamal", "hooks") %> servers: jobs: From 219f90042aa55b0f302d905a23435a7804bea55d Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 06:46:25 +0100 Subject: [PATCH 11/49] Remove gem that now lives in GitHub --- gems/fizzy-saas/.github/dependabot.yml | 12 -- gems/fizzy-saas/.github/workflows/ci.yml | 69 ---------- gems/fizzy-saas/.gitignore | 10 -- gems/fizzy-saas/.kamal/hooks/post-deploy | 17 --- gems/fizzy-saas/.kamal/hooks/pre-build | 37 ----- gems/fizzy-saas/.kamal/hooks/pre-connect | 82 ----------- gems/fizzy-saas/.rubocop.yml | 8 -- gems/fizzy-saas/Gemfile | 6 - gems/fizzy-saas/Gemfile.lock | 69 ---------- gems/fizzy-saas/README.md | 28 ---- gems/fizzy-saas/Rakefile | 8 -- .../app/assets/images/fizzy/saas/.keep | 0 .../stylesheets/fizzy/saas/application.css | 15 --- .../fizzy-saas/app/controllers/concerns/.keep | 0 .../signup/completions_controller.rb | 24 ---- gems/fizzy-saas/app/models/concerns/.keep | 0 gems/fizzy-saas/app/models/signup.rb | 127 ------------------ .../models/signup/account_name_generator.rb | 53 -------- gems/fizzy-saas/app/models/subscription.rb | 13 -- .../layouts/fizzy/saas/application.html.erb | 17 --- .../app/views/signup/completions/new.html.erb | 30 ----- gems/fizzy-saas/app/views/signup/new.html.erb | 28 ---- gems/fizzy-saas/bin/rails | 14 -- gems/fizzy-saas/bin/rubocop | 8 -- gems/fizzy-saas/config/database.yml | 100 -------------- gems/fizzy-saas/config/deploy.beta.yml | 53 -------- gems/fizzy-saas/config/deploy.dhh.yml | 12 -- gems/fizzy-saas/config/deploy.production.yml | 69 ---------- gems/fizzy-saas/config/deploy.staging.yml | 69 ---------- gems/fizzy-saas/config/deploy.yml | 35 ----- gems/fizzy-saas/config/routes.rb | 9 -- gems/fizzy-saas/fizzy-saas.gemspec | 27 ---- gems/fizzy-saas/lib/fizzy/saas.rb | 7 - gems/fizzy-saas/lib/fizzy/saas/engine.rb | 31 ----- gems/fizzy-saas/lib/fizzy/saas/metrics.rb | 13 -- .../lib/fizzy/saas/transaction_pinning.rb | 65 --------- gems/fizzy-saas/lib/fizzy/saas/version.rb | 5 - .../lib/tasks/fizzy/saas_tasks.rake | 4 - gems/fizzy-saas/test/controllers/.keep | 0 .../signups/completions_controller_test.rb | 43 ------ gems/fizzy-saas/test/fixtures/files/.keep | 0 gems/fizzy-saas/test/helpers/.keep | 0 gems/fizzy-saas/test/integration/.keep | 0 gems/fizzy-saas/test/mailers/.keep | 0 gems/fizzy-saas/test/models/.keep | 0 .../signup/account_name_generator_test.rb | 61 --------- gems/fizzy-saas/test/models/signup_test.rb | 53 -------- gems/fizzy-saas/test/test_helper.rb | 9 -- 48 files changed, 1340 deletions(-) delete mode 100644 gems/fizzy-saas/.github/dependabot.yml delete mode 100644 gems/fizzy-saas/.github/workflows/ci.yml delete mode 100644 gems/fizzy-saas/.gitignore delete mode 100755 gems/fizzy-saas/.kamal/hooks/post-deploy delete mode 100755 gems/fizzy-saas/.kamal/hooks/pre-build delete mode 100755 gems/fizzy-saas/.kamal/hooks/pre-connect delete mode 100644 gems/fizzy-saas/.rubocop.yml delete mode 100644 gems/fizzy-saas/Gemfile delete mode 100644 gems/fizzy-saas/Gemfile.lock delete mode 100644 gems/fizzy-saas/README.md delete mode 100644 gems/fizzy-saas/Rakefile delete mode 100644 gems/fizzy-saas/app/assets/images/fizzy/saas/.keep delete mode 100644 gems/fizzy-saas/app/assets/stylesheets/fizzy/saas/application.css delete mode 100644 gems/fizzy-saas/app/controllers/concerns/.keep delete mode 100644 gems/fizzy-saas/app/controllers/signup/completions_controller.rb delete mode 100644 gems/fizzy-saas/app/models/concerns/.keep delete mode 100644 gems/fizzy-saas/app/models/signup.rb delete mode 100644 gems/fizzy-saas/app/models/signup/account_name_generator.rb delete mode 100644 gems/fizzy-saas/app/models/subscription.rb delete mode 100644 gems/fizzy-saas/app/views/layouts/fizzy/saas/application.html.erb delete mode 100644 gems/fizzy-saas/app/views/signup/completions/new.html.erb delete mode 100644 gems/fizzy-saas/app/views/signup/new.html.erb delete mode 100755 gems/fizzy-saas/bin/rails delete mode 100755 gems/fizzy-saas/bin/rubocop delete mode 100644 gems/fizzy-saas/config/database.yml delete mode 100644 gems/fizzy-saas/config/deploy.beta.yml delete mode 100644 gems/fizzy-saas/config/deploy.dhh.yml delete mode 100644 gems/fizzy-saas/config/deploy.production.yml delete mode 100644 gems/fizzy-saas/config/deploy.staging.yml delete mode 100644 gems/fizzy-saas/config/deploy.yml delete mode 100644 gems/fizzy-saas/config/routes.rb delete mode 100644 gems/fizzy-saas/fizzy-saas.gemspec delete mode 100644 gems/fizzy-saas/lib/fizzy/saas.rb delete mode 100644 gems/fizzy-saas/lib/fizzy/saas/engine.rb delete mode 100644 gems/fizzy-saas/lib/fizzy/saas/metrics.rb delete mode 100644 gems/fizzy-saas/lib/fizzy/saas/transaction_pinning.rb delete mode 100644 gems/fizzy-saas/lib/fizzy/saas/version.rb delete mode 100644 gems/fizzy-saas/lib/tasks/fizzy/saas_tasks.rake delete mode 100644 gems/fizzy-saas/test/controllers/.keep delete mode 100644 gems/fizzy-saas/test/controllers/signups/completions_controller_test.rb delete mode 100644 gems/fizzy-saas/test/fixtures/files/.keep delete mode 100644 gems/fizzy-saas/test/helpers/.keep delete mode 100644 gems/fizzy-saas/test/integration/.keep delete mode 100644 gems/fizzy-saas/test/mailers/.keep delete mode 100644 gems/fizzy-saas/test/models/.keep delete mode 100644 gems/fizzy-saas/test/models/signup/account_name_generator_test.rb delete mode 100644 gems/fizzy-saas/test/models/signup_test.rb delete mode 100644 gems/fizzy-saas/test/test_helper.rb diff --git a/gems/fizzy-saas/.github/dependabot.yml b/gems/fizzy-saas/.github/dependabot.yml deleted file mode 100644 index 83610cfa4c..0000000000 --- a/gems/fizzy-saas/.github/dependabot.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 2 -updates: -- package-ecosystem: bundler - directory: "/" - schedule: - interval: weekly - open-pull-requests-limit: 10 -- package-ecosystem: github-actions - directory: "/" - schedule: - interval: weekly - open-pull-requests-limit: 10 diff --git a/gems/fizzy-saas/.github/workflows/ci.yml b/gems/fizzy-saas/.github/workflows/ci.yml deleted file mode 100644 index ef5e97c73e..0000000000 --- a/gems/fizzy-saas/.github/workflows/ci.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: CI - -on: - pull_request: - push: - branches: [ main ] - -jobs: - lint: - runs-on: ubuntu-latest - env: - RUBY_VERSION: ruby-3.4.5 - RUBOCOP_CACHE_ROOT: tmp/rubocop - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ env.RUBY_VERSION }} - bundler-cache: true - - - name: Prepare RuboCop cache - uses: actions/cache@v4 - env: - DEPENDENCIES_HASH: ${{ hashFiles('**/.rubocop.yml', '**/.rubocop_todo.yml', 'Gemfile.lock') }} - with: - path: ${{ env.RUBOCOP_CACHE_ROOT }} - key: rubocop-${{ runner.os }}-${{ env.RUBY_VERSION }}-${{ env.DEPENDENCIES_HASH }}-${{ github.ref_name == github.event.repository.default_branch && github.run_id || 'default' }} - restore-keys: | - rubocop-${{ runner.os }}-${{ env.RUBY_VERSION }}-${{ env.DEPENDENCIES_HASH }}- - - - name: Lint code for consistent style - run: bin/rubocop -f github - - test: - runs-on: ubuntu-latest - - # services: - # redis: - # image: valkey/valkey:8 - # ports: - # - 6379:6379 - # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ruby-3.4.5 - bundler-cache: true - - - name: Run tests - env: - RAILS_ENV: test - # RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - # REDIS_URL: redis://localhost:6379/0 - run: bin/rails db:test:prepare test - - - name: Keep screenshots from failed system tests - uses: actions/upload-artifact@v4 - if: failure() - with: - name: screenshots - path: ${{ github.workspace }}/tmp/screenshots - if-no-files-found: ignore diff --git a/gems/fizzy-saas/.gitignore b/gems/fizzy-saas/.gitignore deleted file mode 100644 index a3ee5aad36..0000000000 --- a/gems/fizzy-saas/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -/.bundle/ -/doc/ -/log/*.log -/pkg/ -/tmp/ -/test/dummy/db/*.sqlite3 -/test/dummy/db/*.sqlite3-* -/test/dummy/log/*.log -/test/dummy/storage/ -/test/dummy/tmp/ diff --git a/gems/fizzy-saas/.kamal/hooks/post-deploy b/gems/fizzy-saas/.kamal/hooks/post-deploy deleted file mode 100755 index 8715060788..0000000000 --- a/gems/fizzy-saas/.kamal/hooks/post-deploy +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -MESSAGE="$KAMAL_PERFORMER deployed $KAMAL_SERVICE_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) - -bin/notify_dash_of_deployment "$MESSAGE" $KAMAL_VERSION $KAMAL_PERFORMER $CURRENT_BRANCH $KAMAL_DESTINATION $KAMAL_RUNTIME - -if [[ $CURRENT_BRANCH == "main" && $KAMAL_DESTINATION == "production" ]]; then - gh release create $KAMAL_SERVICE_VERSION --target $KAMAL_VERSION --generate-notes 2> /dev/null || true - - RELEASE_URL=$(gh release view $KAMAL_SERVICE_VERSION --json url,body --jq .url) - RELEASE_BODY=$(gh release view $KAMAL_SERVICE_VERSION --json url,body --jq .body) - - bin/broadcast_to_bc "$MESSAGE "$'\n'"$RELEASE_URL "$'\n'"$RELEASE_BODY" -else - bin/broadcast_to_bc "$MESSAGE" -fi diff --git a/gems/fizzy-saas/.kamal/hooks/pre-build b/gems/fizzy-saas/.kamal/hooks/pre-build deleted file mode 100755 index 99de8a8248..0000000000 --- a/gems/fizzy-saas/.kamal/hooks/pre-build +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env ruby - -def exit_with_error(message) - $stderr.puts message - exit 1 -end - -def check_branch - if ENV["KAMAL_DESTINATION"] == "production" - current_branch = `git branch --show-current`.strip - - if current_branch != "main" - exit_with_error "Only the `main` branch should be deployed to production, current branch is #{current_branch}. If this is expected, try again with `SKIP_GIT_CHECKS=1` prepended to the command" - end - end -end - -def check_for_uncommitted_changes - if `git status --porcelain`.strip.length != 0 - exit_with_error "You have uncommitted changes, aborting" - end -end - -def check_local_and_remote_heads_match - remote_head = `git ls-remote origin --tags $(git branch --show-current) | cut -f1 | head -1`.strip - local_head = `git rev-parse HEAD`.strip - - if local_head != remote_head - exit_with_error "Remote HEAD #{remote_head}, differs from local HEAD #{local_head}, aborting" - end -end - -unless ENV["SKIP_GIT_CHECKS"] - check_branch - check_for_uncommitted_changes - check_local_and_remote_heads_match -end diff --git a/gems/fizzy-saas/.kamal/hooks/pre-connect b/gems/fizzy-saas/.kamal/hooks/pre-connect deleted file mode 100755 index 5f42f2cf2b..0000000000 --- a/gems/fizzy-saas/.kamal/hooks/pre-connect +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env bash - -# Validate hostnames are FQDNs ending in -int.37signals.com -if command -v yq >/dev/null 2>&1; then - declare -A SUGGESTIONS - while IFS= read -r host; do - if [[ ! $host =~ -int\.37signals\.com$ ]]; then - if [[ $host =~ -4[0-9]{2}$ ]]; then - SUGGESTIONS["$host"]="$host.df-ams-int.37signals.com" - elif [[ $host =~ -1[0-9]{2}$ ]]; then - SUGGESTIONS["$host"]="$host.df-iad-int.37signals.com" - else - SUGGESTIONS["$host"]="$host.sc-chi-int.37signals.com" - fi - fi - done < <(bin/kamal config -d "${KAMAL_DESTINATION:-production}" 2>/dev/null | yq -r '.":hosts"[]') - - if [ ${#SUGGESTIONS[@]} -gt 0 ]; then - echo "Unqualified hostnames found in config/deploy.${KAMAL_DESTINATION:-production}.yml:" >&2 - echo "" >&2 - echo "Update to use fully-qualified hostnames:" >&2 - for host in "${!SUGGESTIONS[@]}"; do - echo " $host → ${SUGGESTIONS[$host]}" >&2 - done - exit 1 - fi -fi - -# Verify Tailscale connection and SSH authentication before deploying. -tailscale_cmd() { - if command -v tailscale >/dev/null 2>&1; then - tailscale "$@" - elif [ -f "/Applications/Tailscale.app/Contents/MacOS/Tailscale" ]; then - env TAILSCALE_BE_CLI=1 /Applications/Tailscale.app/Contents/MacOS/Tailscale "$@" - else - return 1 - fi -} - -on_tailscale() { - tailscale_cmd status --json 2>/dev/null | jq -e '.Self.Online' >/dev/null 2>&1 -} - -# Check Tailscale connection -if ! on_tailscale; then - echo "" >&2 - echo "You must be connected to Tailscale to deploy." >&2 - echo "" >&2 - echo "→ Connect to Tailscale and try again" >&2 - echo "" >&2 - exit 1 -fi - -# Verify SSH access -echo "Deploying via Tailscale. Verifying SSH access…" >&2 - -TEST_HOST="fizzy-app-101" - -SSH_OUTPUT=$(ssh -o ConnectTimeout=5 "app@$TEST_HOST" true 2>&1) -SSH_EXIT=$? - -echo "$SSH_OUTPUT" >&2 - -if echo "$SSH_OUTPUT" | grep -q "Permission denied"; then - GITHUB_USER=$(gh api user 2>/dev/null | jq -r '.login // "unknown"') - GITHUB_KEYS_URL="https://github.com/${GITHUB_USER}.keys" - - echo "" >&2 - echo "ERROR: SSH authentication failed" >&2 - echo "" >&2 - echo "You must deploy with an SSH key that's on your GitHub account." >&2 - echo "" >&2 - echo "→ Verify your public key is at $GITHUB_KEYS_URL" >&2 - echo " Add it at https://github.com/settings/keys if not" >&2 - echo "" >&2 - echo "Note that SSH keys are pulled from GitHub every 5 minutes, so if you've" >&2 - echo "just added a new key to GitHub, try again in five." >&2 - echo "" >&2 - exit 1 -fi - -exit $SSH_EXIT diff --git a/gems/fizzy-saas/.rubocop.yml b/gems/fizzy-saas/.rubocop.yml deleted file mode 100644 index f9d86d4a54..0000000000 --- a/gems/fizzy-saas/.rubocop.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Omakase Ruby styling for Rails -inherit_gem: { rubocop-rails-omakase: rubocop.yml } - -# Overwrite or add rules to create your own house style -# -# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` -# Layout/SpaceInsideArrayLiteralBrackets: -# Enabled: false diff --git a/gems/fizzy-saas/Gemfile b/gems/fizzy-saas/Gemfile deleted file mode 100644 index 396a3192e0..0000000000 --- a/gems/fizzy-saas/Gemfile +++ /dev/null @@ -1,6 +0,0 @@ -source "https://rubygems.org" -git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } - -# 37id and Queenbee integration -gem "queenbee", bc: "queenbee-plugin", ref: "eb01c697de1ad028afc65cc7d9b5345a7a8e849f" -gem "activeresource", require: "active_resource" # needed by queenbee diff --git a/gems/fizzy-saas/Gemfile.lock b/gems/fizzy-saas/Gemfile.lock deleted file mode 100644 index 5d83987cde..0000000000 --- a/gems/fizzy-saas/Gemfile.lock +++ /dev/null @@ -1,69 +0,0 @@ -GIT - remote: https://github.com/basecamp/queenbee-plugin - revision: eb01c697de1ad028afc65cc7d9b5345a7a8e849f - ref: eb01c697de1ad028afc65cc7d9b5345a7a8e849f - specs: - queenbee (3.2.0) - activeresource - builder - rexml - -GEM - remote: https://rubygems.org/ - specs: - activemodel (8.0.2.1) - activesupport (= 8.0.2.1) - activemodel-serializers-xml (1.0.3) - activemodel (>= 5.0.0.a) - activesupport (>= 5.0.0.a) - builder (~> 3.1) - activeresource (6.1.4) - activemodel (>= 6.0) - activemodel-serializers-xml (~> 1.0) - activesupport (>= 6.0) - activesupport (8.0.2.1) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - uri (>= 0.13.1) - base64 (0.3.0) - benchmark (0.4.1) - bigdecimal (3.2.3) - builder (3.3.0) - concurrent-ruby (1.3.5) - connection_pool (2.5.4) - drb (2.2.3) - i18n (1.14.7) - concurrent-ruby (~> 1.0) - logger (1.7.0) - minitest (5.25.5) - rexml (3.4.4) - securerandom (0.4.1) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - uri (1.0.3) - -PLATFORMS - aarch64-linux-gnu - aarch64-linux-musl - arm-linux-gnu - arm-linux-musl - arm64-darwin - x86_64-darwin - x86_64-linux-gnu - x86_64-linux-musl - -DEPENDENCIES - activeresource - queenbee! - -BUNDLED WITH - 2.7.0 diff --git a/gems/fizzy-saas/README.md b/gems/fizzy-saas/README.md deleted file mode 100644 index ecaa3ede6f..0000000000 --- a/gems/fizzy-saas/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Fizzy::Saas -Short description and motivation. - -## Usage -How to use my plugin. - -## Installation -Add this line to your application's Gemfile: - -```ruby -gem "fizzy-saas" -``` - -And then execute: -```bash -$ bundle -``` - -Or install it yourself as: -```bash -$ gem install fizzy-saas -``` - -## Contributing -Contribution directions go here. - -## License -The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/gems/fizzy-saas/Rakefile b/gems/fizzy-saas/Rakefile deleted file mode 100644 index e7793b5c12..0000000000 --- a/gems/fizzy-saas/Rakefile +++ /dev/null @@ -1,8 +0,0 @@ -require "bundler/setup" - -APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__) -load "rails/tasks/engine.rake" - -load "rails/tasks/statistics.rake" - -require "bundler/gem_tasks" diff --git a/gems/fizzy-saas/app/assets/images/fizzy/saas/.keep b/gems/fizzy-saas/app/assets/images/fizzy/saas/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/app/assets/stylesheets/fizzy/saas/application.css b/gems/fizzy-saas/app/assets/stylesheets/fizzy/saas/application.css deleted file mode 100644 index 0ebd7fe829..0000000000 --- a/gems/fizzy-saas/app/assets/stylesheets/fizzy/saas/application.css +++ /dev/null @@ -1,15 +0,0 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS - * files in this directory. Styles in this file should be added after the last require_* statement. - * It is generally better to create a new file per style scope. - * - *= require_tree . - *= require_self - */ diff --git a/gems/fizzy-saas/app/controllers/concerns/.keep b/gems/fizzy-saas/app/controllers/concerns/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/app/controllers/signup/completions_controller.rb b/gems/fizzy-saas/app/controllers/signup/completions_controller.rb deleted file mode 100644 index d7f09c0865..0000000000 --- a/gems/fizzy-saas/app/controllers/signup/completions_controller.rb +++ /dev/null @@ -1,24 +0,0 @@ -class Signup::CompletionsController < ApplicationController - layout "public" - - disallow_account_scope - - def new - @signup = Signup.new(identity: Current.identity) - end - - def create - @signup = Signup.new(signup_params) - - if @signup.complete - redirect_to landing_url(script_name: @signup.account.slug) - else - render :new, status: :unprocessable_entity - end - end - - private - def signup_params - params.expect(signup: %i[ full_name ]).with_defaults(identity: Current.identity) - end -end diff --git a/gems/fizzy-saas/app/models/concerns/.keep b/gems/fizzy-saas/app/models/concerns/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/app/models/signup.rb b/gems/fizzy-saas/app/models/signup.rb deleted file mode 100644 index ca76f5b081..0000000000 --- a/gems/fizzy-saas/app/models/signup.rb +++ /dev/null @@ -1,127 +0,0 @@ -class Signup - include ActiveModel::Model - include ActiveModel::Attributes - include ActiveModel::Validations - - attr_accessor :full_name, :email_address, :identity - attr_reader :queenbee_account, :account, :user - - with_options on: :completion do - validates_presence_of :full_name, :identity - end - - def initialize(...) - @full_name = nil - @email_address = nil - @account = nil - @user = nil - @queenbee_account = nil - @identity = nil - - super - - @email_address = @identity.email_address if @identity - end - - def create_identity - @identity = Identity.find_or_create_by!(email_address: email_address) - @identity.send_magic_link - end - - def complete - if valid?(:completion) - begin - create_queenbee_account - create_account - - true - rescue => error - destroy_account - destroy_queenbee_account - - errors.add(:base, "Something went wrong, and we couldn't create your account. Please give it another try.") - Rails.error.report(error, severity: :error) - Rails.logger.error error - Rails.logger.error error.backtrace.join("\n") - - false - end - else - false - end - end - - private - def create_queenbee_account - @account_name = AccountNameGenerator.new(identity: identity, name: full_name).generate - @queenbee_account = Queenbee::Remote::Account.create!(queenbee_account_attributes) - @tenant = queenbee_account.id.to_s - end - - def destroy_queenbee_account - @queenbee_account&.cancel - @queenbee_account = nil - end - - def create_account - @account = Account.create_with_admin_user( - account: { - external_account_id: @tenant, - name: @account_name - }, - owner: { - name: full_name, - identity: identity - } - ) - @user = @account.users.find_by!(role: :admin) - @account.setup_customer_template - end - - def destroy_account - @account&.destroy! - - @user = nil - @account = nil - @tenant = nil - end - - def queenbee_account_attributes - {}.tap do |attributes| - attributes[:product_name] = "fizzy" - attributes[:name] = @account_name - attributes[:owner_name] = full_name - attributes[:owner_email] = email_address - - attributes[:trial] = true - attributes[:subscription] = subscription_attributes - attributes[:remote_request] = request_attributes - - # # TODO: Terms of Service - # attributes[:terms_of_service] = true - - # We've confirmed the email - attributes[:auto_allow] = true - - # Tell Queenbee to skip the request to create a local account. We've created it ourselves. - attributes[:skip_remote] = true - end - end - - def subscription_attributes - subscription = FreeV1Subscription - - {}.tap do |attributes| - attributes[:name] = subscription.to_param - attributes[:price] = subscription.price - end - end - - def request_attributes - {}.tap do |attributes| - attributes[:remote_address] = Current.ip_address - attributes[:user_agent] = Current.user_agent - attributes[:referrer] = Current.referrer - end - end -end diff --git a/gems/fizzy-saas/app/models/signup/account_name_generator.rb b/gems/fizzy-saas/app/models/signup/account_name_generator.rb deleted file mode 100644 index a6844d3a3a..0000000000 --- a/gems/fizzy-saas/app/models/signup/account_name_generator.rb +++ /dev/null @@ -1,53 +0,0 @@ -class Signup::AccountNameGenerator - SUFFIX = "Fizzy".freeze - - attr_reader :identity, :name - - def initialize(identity:, name:) - @identity = identity - @name = name - end - - def generate - next_index = current_index + 1 - - if next_index == 1 - "#{prefix} #{SUFFIX}" - else - "#{prefix} #{next_index.ordinalize} #{SUFFIX}" - end - end - - private - def current_index - existing_indices.max || 0 - end - - def existing_indices - Current.without_account do - identity.accounts.filter_map do |account| - if account.name.match?(first_account_name_regex) - 1 - elsif match = account.name.match(nth_account_name_regex) - match[1].to_i - end - end - end - end - - def first_account_name_regex - @first_account_name_regex ||= /\A#{prefix}\s+#{SUFFIX}\Z/i - end - - def nth_account_name_regex - @nth_account_name_regex ||= /\A#{prefix}\s+(1st|2nd|3rd|\d+th)\s+#{SUFFIX}/i - end - - def prefix - @prefix ||= "#{first_name}'s" - end - - def first_name - name.strip.split(" ", 2).first - end -end diff --git a/gems/fizzy-saas/app/models/subscription.rb b/gems/fizzy-saas/app/models/subscription.rb deleted file mode 100644 index 5efb1a7fac..0000000000 --- a/gems/fizzy-saas/app/models/subscription.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Subscription < Queenbee::Subscription - SHORT_NAMES = %w[ FreeV1 ] - - def self.short_name - name.demodulize - end - - class FreeV1 < Subscription - property :proper_name, "Free Subscription" - property :price, 0 - property :frequency, "yearly" - end -end diff --git a/gems/fizzy-saas/app/views/layouts/fizzy/saas/application.html.erb b/gems/fizzy-saas/app/views/layouts/fizzy/saas/application.html.erb deleted file mode 100644 index 144b378387..0000000000 --- a/gems/fizzy-saas/app/views/layouts/fizzy/saas/application.html.erb +++ /dev/null @@ -1,17 +0,0 @@ - - - - Fizzy saas - <%= csrf_meta_tags %> - <%= csp_meta_tag %> - - <%= yield :head %> - - <%= stylesheet_link_tag "fizzy/saas/application", media: "all" %> - - - -<%= yield %> - - - diff --git a/gems/fizzy-saas/app/views/signup/completions/new.html.erb b/gems/fizzy-saas/app/views/signup/completions/new.html.erb deleted file mode 100644 index aee8f45869..0000000000 --- a/gems/fizzy-saas/app/views/signup/completions/new.html.erb +++ /dev/null @@ -1,30 +0,0 @@ -<% @page_title = "Complete your sign-up" %> - -
"> -

<%= @page_title %>

- - <%= form_with model: @signup, url: saas.signup_completion_path, scope: "signup", class: "flex flex-column gap", data: { controller: "form" } do |form| %> - <%= form.text_field :full_name, class: "input txt-large", autocomplete: "name", placeholder: "Enter your full name…", autofocus: true, required: true %> - -

You’re one step away. Just enter your name to get your own Fizzy account.

- - <% if @signup.errors.any? %> -
-
    - <% @signup.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - - - <% end %> -
- -<% content_for :footer do %> - <%= render "sessions/footer" %> -<% end %> diff --git a/gems/fizzy-saas/app/views/signup/new.html.erb b/gems/fizzy-saas/app/views/signup/new.html.erb deleted file mode 100644 index 93166dd2fe..0000000000 --- a/gems/fizzy-saas/app/views/signup/new.html.erb +++ /dev/null @@ -1,28 +0,0 @@ -<% @page_title = "Sign up for Fizzy" %> - -
"> -

Sign up

- - <%= form_with model: @signup, url: saas.signup_path, scope: "signup", class: "flex flex-column gap", data: { turbo: false, controller: "form" } do |form| %> - <%= form.email_field :email_address, class: "input", autocomplete: "username", placeholder: "Email address", required: true %> - - <% if @signup.errors.any? %> -
-
    - <% @signup.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - - - <% end %> -
- -<% content_for :footer do %> - <%= render "sessions/footer" %> -<% end %> diff --git a/gems/fizzy-saas/bin/rails b/gems/fizzy-saas/bin/rails deleted file mode 100755 index 42a0e5bce3..0000000000 --- a/gems/fizzy-saas/bin/rails +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env ruby -# This command will automatically be run when you run "rails" with Rails gems -# installed from the root of your application. - -ENGINE_ROOT = File.expand_path("..", __dir__) -ENGINE_PATH = File.expand_path("../lib/fizzy/saas/engine", __dir__) -APP_PATH = File.expand_path("../test/dummy/config/application", __dir__) - -# Set up gems listed in the Gemfile. -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) - -require "rails/all" -require "rails/engine/commands" diff --git a/gems/fizzy-saas/bin/rubocop b/gems/fizzy-saas/bin/rubocop deleted file mode 100755 index 40330c0ff1..0000000000 --- a/gems/fizzy-saas/bin/rubocop +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby -require "rubygems" -require "bundler/setup" - -# explicit rubocop config increases performance slightly while avoiding config confusion. -ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) - -load Gem.bin_path("rubocop", "rubocop") diff --git a/gems/fizzy-saas/config/database.yml b/gems/fizzy-saas/config/database.yml deleted file mode 100644 index 3c68fd0810..0000000000 --- a/gems/fizzy-saas/config/database.yml +++ /dev/null @@ -1,100 +0,0 @@ -<% - if ENV["MIGRATE"].present? - mysql_app_user_key = "MYSQL_ALTER_USER" - mysql_app_password_key = "MYSQL_ALTER_PASSWORD" - else - mysql_app_user_key = "MYSQL_APP_USER" - mysql_app_password_key = "MYSQL_APP_PASSWORD" - end - - mysql_app_user = ENV[mysql_app_user_key] - mysql_app_password = ENV[mysql_app_password_key] -%> - -default: &default - adapter: trilogy - host: <%= ENV.fetch "FIZZY_DB_HOST", "127.0.0.1" %> - port: <%= ENV.fetch "FIZZY_DB_PORT", 3306 %> - pool: 50 - timeout: 5000 - variables: - transaction_isolation: READ-COMMITTED - -development: - primary: - <<: *default - database: fizzy_development - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: - <<: *default - database: fizzy_development - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: true - cable: - <<: *default - database: development_cable - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/cable_migrate - cache: - <<: *default - database: development_cache - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/cache_migrate - queue: - <<: *default - database: development_queue - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - migrations_paths: db/queue_migrate - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - primary: - <<: *default - database: fizzy_test - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: - <<: *default - database: fizzy_test - port: <%= ENV.fetch "FIZZY_DB_PORT", 33380 %> - replica: true - -production: &production - primary: - <<: *default - database: fizzy_production - host: <%= ENV["MYSQL_DATABASE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - replica: - <<: *default - database: fizzy_production - host: <%= ENV["MYSQL_DATABASE_REPLICA_HOST"] %> - username: <%= ENV["MYSQL_READONLY_USER"] %> - password: <%= ENV["MYSQL_READONLY_PASSWORD"] %> - replica: true - cable: - <<: *default - database: fizzy_solidcable_production - host: <%= ENV["MYSQL_SOLID_CABLE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/cable_migrate - queue: - <<: *default - database: fizzy_solidqueue_production - host: <%= ENV["MYSQL_SOLID_QUEUE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/queue_migrate - cache: - <<: *default - database: fizzy_solidcache_production - host: <%= ENV["MYSQL_SOLID_CACHE_HOST"] %> - username: <%= mysql_app_user %> - password: <%= mysql_app_password %> - migrations_paths: db/cache_migrate - -beta: *production -staging: *production diff --git a/gems/fizzy-saas/config/deploy.beta.yml b/gems/fizzy-saas/config/deploy.beta.yml deleted file mode 100644 index 4c4c323f61..0000000000 --- a/gems/fizzy-saas/config/deploy.beta.yml +++ /dev/null @@ -1,53 +0,0 @@ -servers: - web: - hosts: - - fizzy-beta-app-01.sc-chi-int.37signals.com: sc_chi - - fizzy-beta-app-101.df-iad-int.37signals.com: df_iad - labels: - otel_scrape_enabled: true - -# we don't run the jobs role in beta -allow_empty_roles: true - -proxy: - ssl: false - -ssh: - user: app - -env: - clear: - RAILS_ENV: beta - MYSQL_DATABASE_HOST: fizzy-mysql-primary - MYSQL_DATABASE_REPLICA_HOST: fizzy-mysql-replica - MYSQL_SOLID_CABLE_HOST: fizzy-mysql-primary - MYSQL_SOLID_QUEUE_HOST: fizzy-mysql-primary - MYSQL_SOLID_CACHE_HOST: fizzy-beta-solidcache-db-101 - secret: - - RAILS_MASTER_KEY - - MYSQL_ALTER_PASSWORD - - MYSQL_ALTER_USER - - MYSQL_APP_PASSWORD - - MYSQL_APP_USER - - MYSQL_READONLY_PASSWORD - - MYSQL_READONLY_USER - tags: - sc_chi: {} - df_iad: - PRIMARY_DATACENTER: true - df_ams: {} - -accessories: - load-balancer: - image: basecamp/kamal-proxy:lb - host: fizzy-beta-lb-01.sc-chi-int.37signals.com - labels: - otel_role: load-balancer - otel_service: fizzy-load-balancer - otel_scrape_enabled: true - options: - publish: - - 80:80 - - 443:443 - volumes: - - load-balancer:/home/kamal-proxy/.config/kamal-proxy diff --git a/gems/fizzy-saas/config/deploy.dhh.yml b/gems/fizzy-saas/config/deploy.dhh.yml deleted file mode 100644 index 05bf874d41..0000000000 --- a/gems/fizzy-saas/config/deploy.dhh.yml +++ /dev/null @@ -1,12 +0,0 @@ -servers: - web: - hosts: - - test-1 - jobs: - hosts: - - test-1 -env: - clear: - ARTENANT: test - -proxy: false diff --git a/gems/fizzy-saas/config/deploy.production.yml b/gems/fizzy-saas/config/deploy.production.yml deleted file mode 100644 index 285dab819e..0000000000 --- a/gems/fizzy-saas/config/deploy.production.yml +++ /dev/null @@ -1,69 +0,0 @@ -servers: - web: - hosts: - - fizzy-app-01.sc-chi-int.37signals.com: sc_chi - - fizzy-app-02.sc-chi-int.37signals.com: sc_chi - - fizzy-app-101.df-iad-int.37signals.com: df_iad - - fizzy-app-102.df-iad-int.37signals.com: df_iad - - fizzy-app-401.df-ams-int.37signals.com: df_ams - - fizzy-app-402.df-ams-int.37signals.com: df_ams - labels: - otel_scrape_enabled: true - jobs: - hosts: - - fizzy-jobs-101.df-iad-int.37signals.com: df_iad - - fizzy-jobs-102.df-iad-int.37signals.com: df_iad - labels: - otel_scrape_enabled: true - -proxy: - ssl: false - -ssh: - user: app - -env: - clear: - RAILS_ENV: production - MYSQL_DATABASE_HOST: fizzy-mysql-primary - MYSQL_DATABASE_REPLICA_HOST: fizzy-mysql-replica - MYSQL_SOLID_CABLE_HOST: fizzy-mysql-primary - MYSQL_SOLID_QUEUE_HOST: fizzy-mysql-primary - secret: - - RAILS_MASTER_KEY - - MYSQL_ALTER_PASSWORD - - MYSQL_ALTER_USER - - MYSQL_APP_PASSWORD - - MYSQL_APP_USER - - MYSQL_READONLY_PASSWORD - - MYSQL_READONLY_USER - tags: - sc_chi: - MYSQL_SOLID_CACHE_HOST: fizzy-solidcache-db-01.sc-chi-int.37signals.com - df_iad: - MYSQL_SOLID_CACHE_HOST: fizzy-solidcache-db-101.df-iad-int.37signals.com - PRIMARY_DATACENTER: true - df_ams: - MYSQL_SOLID_CACHE_HOST: fizzy-solidcache-db-401.df-ams-int.37signals.com - - -accessories: - load-balancer: - image: basecamp/kamal-proxy:lb - hosts: - - fizzy-lb-101.df-iad-int.37signals.com - - fizzy-lb-01.sc-chi-int.37signals.com - - fizzy-lb-401.df-ams-int.37signals.com - labels: - otel_role: load-balancer - otel_service: fizzy-load-balancer - otel_scrape_enabled: true - options: - publish: - - 80:80 - - 443:443 - # NFS mount for certificates - # See https://3.basecamp.com/2914079/buckets/37331921/todos/9180260061 - mount: type=volume,src=certificates,dst=/certificates,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/fizzy-production-certificates,"volume-opt=o=addr=purestorage.sc-chi-int.37signals.com,nfsvers=3,rw,noatime,nconnect=8,soft,timeo=30,retrans=2" - volumes: - - load-balancer:/home/kamal-proxy/.config/kamal-proxy diff --git a/gems/fizzy-saas/config/deploy.staging.yml b/gems/fizzy-saas/config/deploy.staging.yml deleted file mode 100644 index 3163c7a7bf..0000000000 --- a/gems/fizzy-saas/config/deploy.staging.yml +++ /dev/null @@ -1,69 +0,0 @@ -servers: - web: - hosts: - - fizzy-staging-app-101.df-iad-int.37signals.com: df_iad - - fizzy-staging-app-102.df-iad-int.37signals.com: df_iad - - fizzy-staging-app-01.sc-chi-int.37signals.com: sc_chi - - fizzy-staging-app-02.sc-chi-int.37signals.com: sc_chi - - fizzy-staging-app-401.df-ams-int.37signals.com: df_ams - - fizzy-staging-app-402.df-ams-int.37signals.com: df_ams - labels: - otel_scrape_enabled: true - jobs: - hosts: - - fizzy-staging-jobs-101.df-iad-int.37signals.com: df_iad - - fizzy-staging-jobs-102.df-iad-int.37signals.com: df_iad - labels: - otel_scrape_enabled: true - -proxy: - ssl: false - -ssh: - user: app - -env: - clear: - RAILS_ENV: staging - MYSQL_DATABASE_HOST: fizzy-staging-mysql-primary - MYSQL_DATABASE_REPLICA_HOST: fizzy-staging-mysql-replica - MYSQL_SOLID_CABLE_HOST: fizzy-staging-mysql-primary - MYSQL_SOLID_QUEUE_HOST: fizzy-staging-mysql-primary - secret: - - RAILS_MASTER_KEY - - MYSQL_ALTER_PASSWORD - - MYSQL_ALTER_USER - - MYSQL_APP_PASSWORD - - MYSQL_APP_USER - - MYSQL_READONLY_PASSWORD - - MYSQL_READONLY_USER - tags: - sc_chi: - MYSQL_SOLID_CACHE_HOST: fizzy-staging-solidcache-db-01.sc-chi-int.37signals.com - df_iad: - MYSQL_SOLID_CACHE_HOST: fizzy-staging-solidcache-db-101.df-iad-int.37signals.com - PRIMARY_DATACENTER: true - df_ams: - MYSQL_SOLID_CACHE_HOST: fizzy-staging-solidcache-db-401.df-ams-int.37signals.com - - -accessories: - load-balancer: - image: basecamp/kamal-proxy:lb - hosts: - - fizzy-staging-lb-01.sc-chi-int.37signals.com - - fizzy-staging-lb-101.df-iad-int.37signals.com - - fizzy-staging-lb-401.df-ams-int.37signals.com - labels: - otel_role: load-balancer - otel_service: fizzy-load-balancer - otel_scrape_enabled: true - options: - publish: - - 80:80 - - 443:443 - # NFS mount for certificates - # See https://3.basecamp.com/2914079/buckets/37331921/todos/9180260061 - mount: type=volume,src=certificates,dst=/certificates,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/fizzy-staging-certificates,"volume-opt=o=addr=purestorage.sc-chi-int.37signals.com,nfsvers=3,rw,noatime,nconnect=8,soft,timeo=30,retrans=2" - volumes: - - load-balancer:/home/kamal-proxy/.config/kamal-proxy diff --git a/gems/fizzy-saas/config/deploy.yml b/gems/fizzy-saas/config/deploy.yml deleted file mode 100644 index ea3ca3c49c..0000000000 --- a/gems/fizzy-saas/config/deploy.yml +++ /dev/null @@ -1,35 +0,0 @@ -service: fizzy -image: basecamp/fizzy -asset_path: /rails/public/assets -hooks_path: <%= File.join(Gem::Specification.find_by_name("fizzy-saas").gem_dir, ".kamal", "hooks") %> - -servers: - jobs: - cmd: bin/jobs - -volumes: - - fizzy:/rails/storage - -proxy: - ssl: true - -registry: - server: registry.37signals.com - username: robot$harbor-bot - password: - - BASECAMP_REGISTRY_PASSWORD - -builder: - arch: amd64 - secrets: - - GITHUB_TOKEN - remote: ssh://app@docker-builder-102 - local: <%= ENV.fetch("KAMAL_BUILDER_LOCAL", "true") %> - -env: - secret: - - RAILS_MASTER_KEY - -aliases: - console: app exec -i --reuse "bin/rails console" - ssh: app exec -i --reuse /bin/bash diff --git a/gems/fizzy-saas/config/routes.rb b/gems/fizzy-saas/config/routes.rb deleted file mode 100644 index 2b9639edb2..0000000000 --- a/gems/fizzy-saas/config/routes.rb +++ /dev/null @@ -1,9 +0,0 @@ -Fizzy::Saas::Engine.routes.draw do - get "/signup/new", to: redirect("/session/new") - - namespace :signup do - resource :completion, only: %i[ new create ] - end - - Queenbee.routes(self) -end diff --git a/gems/fizzy-saas/fizzy-saas.gemspec b/gems/fizzy-saas/fizzy-saas.gemspec deleted file mode 100644 index f1b28de00a..0000000000 --- a/gems/fizzy-saas/fizzy-saas.gemspec +++ /dev/null @@ -1,27 +0,0 @@ -require_relative "lib/fizzy/saas/version" - -Gem::Specification.new do |spec| - spec.name = "fizzy-saas" - spec.version = Fizzy::Saas::VERSION - spec.authors = [ "Mike Dalessio" ] - spec.email = [ "mike@37signals.com" ] - spec.homepage = "TODO" - spec.summary = "TODO: Summary of Fizzy::Saas." - spec.description = "TODO: Description of Fizzy::Saas." - - # Prevent pushing this gem to RubyGems.org. To allow pushes either set the "allowed_push_host" - # to allow pushing to a single host or delete this section to allow pushing to any host. - spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." - spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." - - spec.files = Dir.chdir(File.expand_path(__dir__)) do - Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] - end - - spec.add_dependency "rails", ">= 8.1.0.beta1" - spec.add_dependency "queenbee" - spec.add_dependency "rails_structured_logging" -end diff --git a/gems/fizzy-saas/lib/fizzy/saas.rb b/gems/fizzy-saas/lib/fizzy/saas.rb deleted file mode 100644 index dd0d354926..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "fizzy/saas/version" -require "fizzy/saas/engine" - -module Fizzy - module Saas - end -end diff --git a/gems/fizzy-saas/lib/fizzy/saas/engine.rb b/gems/fizzy-saas/lib/fizzy/saas/engine.rb deleted file mode 100644 index 14ce037590..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas/engine.rb +++ /dev/null @@ -1,31 +0,0 @@ -require_relative "metrics" -require_relative "transaction_pinning" - -module Fizzy - module Saas - class Engine < ::Rails::Engine - # moved from config/initializers/queenbee.rb - Queenbee.host_app = Fizzy - - initializer "fizzy_saas.transaction_pinning" do |app| - if ActiveRecord::Base.replica_configured? - app.config.middleware.insert_after( - ActiveRecord::Middleware::DatabaseSelector, - TransactionPinning::Middleware - ) - end - end - - config.to_prepare do - Queenbee::Subscription.short_names = Subscription::SHORT_NAMES - Queenbee::ApiToken.token = Rails.application.credentials.dig(:queenbee_api_token) - - Subscription::SHORT_NAMES.each do |short_name| - const_name = "#{short_name}Subscription" - ::Object.send(:remove_const, const_name) if ::Object.const_defined?(const_name) - ::Object.const_set const_name, Subscription.const_get(short_name, false) - end - end - end - end -end diff --git a/gems/fizzy-saas/lib/fizzy/saas/metrics.rb b/gems/fizzy-saas/lib/fizzy/saas/metrics.rb deleted file mode 100644 index 80a2bc194b..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas/metrics.rb +++ /dev/null @@ -1,13 +0,0 @@ -Yabeda.configure do - SHORT_HISTOGRAM_BUCKETS = [ 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5 ] - - group :fizzy do - counter :replica_stale, - comment: "Number of requests served from a stale replica" - - histogram :replica_wait, - unit: :seconds, - comment: "Time spent waiting for replica to catch up with transaction", - buckets: SHORT_HISTOGRAM_BUCKETS - end -end diff --git a/gems/fizzy-saas/lib/fizzy/saas/transaction_pinning.rb b/gems/fizzy-saas/lib/fizzy/saas/transaction_pinning.rb deleted file mode 100644 index ed3cf2060d..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas/transaction_pinning.rb +++ /dev/null @@ -1,65 +0,0 @@ -module TransactionPinning - class Middleware - SESSION_KEY = :last_txn - DEFAULT_MAX_WAIT = 0.25 - - def initialize(app) - @app = app - @timeout = Rails.application.config.x.transaction_pinning&.timeout&.to_f || DEFAULT_MAX_WAIT - end - - def call(env) - request = ActionDispatch::Request.new(env) - replica_metrics = {} - - if ApplicationRecord.current_role == :reading - wait_for_replica_catchup(request, replica_metrics) - end - - status, headers, body = @app.call(env) - headers.merge!(replica_metrics.transform_values(&:to_s)) - - if ApplicationRecord.current_role == :writing - capture_transaction_id(request) - end - - [ status, headers, body ] - end - - private - def wait_for_replica_catchup(request, replica_metrics) - if last_txn = request.session[SESSION_KEY].presence - has_transaction = tracking_replica_wait_time(replica_metrics) do - replica_has_transaction(last_txn) - end - - unless has_transaction - Yabeda.fizzy.replica_stale.increment - replica_metrics["X-Replica-Stale"] = true - end - end - end - - def capture_transaction_id(request) - request.session[SESSION_KEY] = ApplicationRecord.connection.show_variable("global.gtid_executed") - end - - def replica_has_transaction(txn) - sql = ApplicationRecord.sanitize_sql_array([ "SELECT WAIT_FOR_EXECUTED_GTID_SET(?, ?)", txn, @timeout ]) - ApplicationRecord.connection.select_value(sql) == 0 - rescue => e - Sentry.capture_exception(e, extra: { gtid: txn }) - true # Treat as if we're up to date, since we don't know - end - - def tracking_replica_wait_time(replica_metrics) - started_at = Time.current - - Yabeda.fizzy.replica_wait.measure do - yield - end.tap do - replica_metrics["X-Replica-Wait"] = Time.current - started_at - end - end - end -end diff --git a/gems/fizzy-saas/lib/fizzy/saas/version.rb b/gems/fizzy-saas/lib/fizzy/saas/version.rb deleted file mode 100644 index 7a95d2d052..0000000000 --- a/gems/fizzy-saas/lib/fizzy/saas/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Fizzy - module Saas - VERSION = "0.1.0" - end -end diff --git a/gems/fizzy-saas/lib/tasks/fizzy/saas_tasks.rake b/gems/fizzy-saas/lib/tasks/fizzy/saas_tasks.rake deleted file mode 100644 index 8fe948d94a..0000000000 --- a/gems/fizzy-saas/lib/tasks/fizzy/saas_tasks.rake +++ /dev/null @@ -1,4 +0,0 @@ -# desc "Explaining what the task does" -# task :fizzy_saas do -# # Task goes here -# end diff --git a/gems/fizzy-saas/test/controllers/.keep b/gems/fizzy-saas/test/controllers/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/controllers/signups/completions_controller_test.rb b/gems/fizzy-saas/test/controllers/signups/completions_controller_test.rb deleted file mode 100644 index 49628a188e..0000000000 --- a/gems/fizzy-saas/test/controllers/signups/completions_controller_test.rb +++ /dev/null @@ -1,43 +0,0 @@ -require "test_helper" - -class Signup::CompletionsControllerTest < ActionDispatch::IntegrationTest - setup do - @signup = Signup.new(email_address: "newuser@example.com", full_name: "New User") - - @signup.create_identity || raise("Failed to create identity") - - sign_in_as @signup.identity - end - - test "new" do - untenanted do - get saas.new_signup_completion_path - end - - assert_response :success - end - - test "create" do - untenanted do - post saas.signup_completion_path, params: { - signup: { - full_name: @signup.full_name - } - } - end - - assert_response :redirect, "Valid params should redirect" - end - - test "create with invalid params" do - untenanted do - post saas.signup_completion_path, params: { - signup: { - full_name: "" - } - } - end - - assert_response :unprocessable_entity, "Invalid params should return unprocessable entity" - end -end diff --git a/gems/fizzy-saas/test/fixtures/files/.keep b/gems/fizzy-saas/test/fixtures/files/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/helpers/.keep b/gems/fizzy-saas/test/helpers/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/integration/.keep b/gems/fizzy-saas/test/integration/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/mailers/.keep b/gems/fizzy-saas/test/mailers/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/models/.keep b/gems/fizzy-saas/test/models/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gems/fizzy-saas/test/models/signup/account_name_generator_test.rb b/gems/fizzy-saas/test/models/signup/account_name_generator_test.rb deleted file mode 100644 index d22922b29d..0000000000 --- a/gems/fizzy-saas/test/models/signup/account_name_generator_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -require "test_helper" - -class Signup::AccountNameGeneratorTest < ActiveSupport::TestCase - setup do - @identity = Identity.create!(email_address: "newart.userbaum@example.com") - @name = "Newart userbaum" - @generator = Signup::AccountNameGenerator.new(identity: @identity, name: @name) - end - - test "generate" do - account_name = @generator.generate - assert_equal "Newart's Fizzy", account_name, "The 1st account doesn't have 1st in the name" - - first_account = Account.create!(external_account_id: "1st", name: account_name) - Current.without_account do - @identity.users.create!(account: first_account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 2nd Fizzy", account_name - - second_account = Account.create!(external_account_id: "2nd", name: account_name) - Current.without_account do - @identity.users.create!(account: second_account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 3rd Fizzy", account_name - - third_account = Account.create!(external_account_id: "3rd", name: account_name) - Current.without_account do - @identity.users.create!(account: third_account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 4th Fizzy", account_name - - fourth_account = Account.create!(external_account_id: "4th", name: account_name) - Current.without_account do - @identity.users.create!(account: fourth_account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 5th Fizzy", account_name - end - - test "generate continues from the previous highest index" do - account = Account.create!(external_account_id: "12th", name: "Newart's 12th Fizzy") - Current.without_account do - @identity.users.create!(account: account, name: @name) - @identity.reload - end - - account_name = @generator.generate - assert_equal "Newart's 13th Fizzy", account_name - end -end diff --git a/gems/fizzy-saas/test/models/signup_test.rb b/gems/fizzy-saas/test/models/signup_test.rb deleted file mode 100644 index 69c0e699f9..0000000000 --- a/gems/fizzy-saas/test/models/signup_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require "test_helper" - -class SignupTest < ActiveSupport::TestCase - test "#create_identity" do - signup = Signup.new(email_address: "brian@example.com") - - assert_difference -> { Identity.count }, 1 do - assert_difference -> { MagicLink.count }, 1 do - assert signup.create_identity - end - end - - assert_empty signup.errors - assert signup.identity - assert signup.identity.persisted? - - signup_existing = Signup.new(email_address: "brian@example.com") - - assert_no_difference -> { Identity.count } do - assert_difference -> { MagicLink.count }, 1 do - assert signup_existing.create_identity, "Should send magic link for existing identity" - end - end - - signup_invalid = Signup.new(email_address: "") - assert_raises do - signup_invalid.create_identity - end - end - - test "#complete" do - Account.any_instance.expects(:setup_customer_template).once - Current.without_account do - signup = Signup.new( - full_name: "Kevin", - identity: identities(:kevin) - ) - - assert signup.complete - - assert signup.account - assert signup.user - assert_equal "Kevin", signup.user.name - - signup_invalid = Signup.new( - full_name: "", - identity: identities(:kevin) - ) - assert_not signup_invalid.complete - assert_not_empty signup_invalid.errors[:full_name] - end - end -end diff --git a/gems/fizzy-saas/test/test_helper.rb b/gems/fizzy-saas/test/test_helper.rb deleted file mode 100644 index eeaf249547..0000000000 --- a/gems/fizzy-saas/test/test_helper.rb +++ /dev/null @@ -1,9 +0,0 @@ -require "queenbee/testing/mocks" - -Queenbee::Remote::Account.class_eval do - # because we use the account ID as the tenant name, we need it to be unique in each test to avoid - # parallelized tests clobbering each other. - def next_id - super + Random.rand(1000000) - end -end From 87473f4edda50e3f2928b13742c38275fa4342c2 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 07:39:04 +0100 Subject: [PATCH 12/49] Don't choke if no structured logging Temporary workaround, we need a better solution here. --- app/controllers/concerns/authentication.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index f4ad6a0131..4ffca9cb02 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -81,7 +81,8 @@ def start_new_session_for(identity) end def set_current_session(session) - logger.struct " Authorized Identity##{session.identity.id}", authentication: { identity: { id: session.identity.id } } + # TODO: Release structured logging or look for alternative + logger.try :struct, " Authorized Identity##{session.identity.id}", authentication: { identity: { id: session.identity.id } } Current.session = session cookies.signed.permanent[:session_token] = { value: session.signed_id, httponly: true, same_site: :lax } end From 429a70dacc4b43bad86635ca94684fd4660fab83 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 07:39:19 +0100 Subject: [PATCH 13/49] Add CI step for saas tests --- config/ci.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/ci.rb b/config/ci.rb index 689060927b..df7f3aed2e 100644 --- a/config/ci.rb +++ b/config/ci.rb @@ -1,5 +1,7 @@ # Run using bin/ci +require_relative "../lib/fizzy" + CI.run do step "Setup", "bin/setup --skip-server" @@ -9,7 +11,9 @@ step "Security: Importmap audit", "bin/importmap audit" step "Security: Brakeman audit", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error" - step "Tests: Rails", "bin/rails test" + step "Tests: Fizzy", "bin/rails test" + + step "Tests: SaaS", "SAAS=1 bin/rails test:saas" if Fizzy.saas? step "Tests: System", "bin/rails test:system" if success? From 597d0187aff2ac9859e7b95b08c22fbe10a2a9ad Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 07:39:26 +0100 Subject: [PATCH 14/49] Format --- lib/fizzy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fizzy.rb b/lib/fizzy.rb index 0a87dd8154..614add8ecb 100644 --- a/lib/fizzy.rb +++ b/lib/fizzy.rb @@ -11,7 +11,7 @@ def db_adapter def configure_bundle if saas? - ENV["BUNDLE_WITH"] = [ENV["BUNDLE_WITH"], "saas"].compact.join(",") + ENV["BUNDLE_WITH"] = [ ENV["BUNDLE_WITH"], "saas" ].compact.join(",") end end end From 78e763f8f770389007340249efb686b1796eb0f5 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 07:39:33 +0100 Subject: [PATCH 15/49] Make structured logging private --- Gemfile | 6 +++--- Gemfile.lock | 17 +++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Gemfile b/Gemfile index 5a5f86f78f..66c3f901fd 100644 --- a/Gemfile +++ b/Gemfile @@ -40,7 +40,6 @@ gem "useragent", bc: "useragent" gem "mission_control-jobs" gem "sentry-ruby" gem "sentry-rails" -gem "rails_structured_logging", bc: "rails-structured-logging" gem "yabeda" gem "yabeda-actioncable" gem "yabeda-activejob", github: "basecamp/yabeda-activejob", branch: "bulk-and-scheduled-jobs" @@ -78,6 +77,7 @@ end group :saas, optional: true do gem "activeresource", require: "active_resource" - gem "queenbee", git: "https://github.com/basecamp/queenbee-plugin" - gem "fizzy-saas", path: "gems/fizzy-saas" + gem "queenbee", bc: "queenbee-plugin" + gem "fizzy-saas", bc: "fizzy-saas" + gem "rails_structured_logging", bc: "rails-structured-logging" end diff --git a/Gemfile.lock b/Gemfile.lock index 6e8bbd96b5..25d29711c1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/basecamp/fizzy-saas + revision: 00de8e2e1ae8e9df5e0a2acf4593a4e9a824c5c8 + specs: + fizzy-saas (0.1.0) + queenbee + rails (>= 8.1.0.beta1) + rails_structured_logging + GIT remote: https://github.com/basecamp/queenbee-plugin revision: 15faf03a876c5e66b67753d2e1ddb24f1eb5abb2 @@ -131,14 +140,6 @@ GIT tsort (>= 0.2) zeitwerk (~> 2.6) -PATH - remote: gems/fizzy-saas - specs: - fizzy-saas (0.1.0) - queenbee - rails (>= 8.1.0.beta1) - rails_structured_logging - GEM remote: https://rubygems.org/ specs: From 837c56d15ed601e6cb4343a0008a8824ef7dd1f4 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 09:22:49 +0100 Subject: [PATCH 16/49] Remove comment --- config/routes.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 87fe32b7a3..48741a7a94 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -224,7 +224,6 @@ get "manifest" => "rails/pwa#manifest", as: :pwa_manifest get "service-worker" => "pwa#service_worker" - # TODO: Can we move this just to the engine if Fizzy.saas? mount Fizzy::Saas::Engine, at: "/", as: "saas" end From 1667bcd7554249993662b2453ae039ba89de19fa Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 09:32:23 +0100 Subject: [PATCH 17/49] The engine automounts now --- config/routes.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 48741a7a94..3afb093a77 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -224,10 +224,6 @@ get "manifest" => "rails/pwa#manifest", as: :pwa_manifest get "service-worker" => "pwa#service_worker" - if Fizzy.saas? - mount Fizzy::Saas::Engine, at: "/", as: "saas" - end - namespace :admin do mount MissionControl::Jobs::Engine, at: "/jobs" get "stats", to: "stats#show" From ea9fd89ea82feda6c1948350572103faaaa32a75 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 17:46:54 +0100 Subject: [PATCH 18/49] Instead of a dedicated group, use a completely separate Gemfile for the saas version The group-based approach won't work if you don't have access to the gems! --- Gemfile | 7 - Gemfile.lock | 4 - Gemfile.saas | 8 + Gemfile.saas.lock | 645 ++++++++++++++++++++++++++++++++++++++++++++++ lib/fizzy.rb | 2 +- 5 files changed, 654 insertions(+), 12 deletions(-) create mode 100644 Gemfile.saas create mode 100644 Gemfile.saas.lock diff --git a/Gemfile b/Gemfile index 66c3f901fd..a4cb0a29fe 100644 --- a/Gemfile +++ b/Gemfile @@ -74,10 +74,3 @@ group :test do gem "vcr" gem "mocha" end - -group :saas, optional: true do - gem "activeresource", require: "active_resource" - gem "queenbee", bc: "queenbee-plugin" - gem "fizzy-saas", bc: "fizzy-saas" - gem "rails_structured_logging", bc: "rails-structured-logging" -end diff --git a/Gemfile.lock b/Gemfile.lock index 25d29711c1..6556fd3320 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -585,7 +585,6 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES - activeresource autotuner aws-sdk-s3 bcrypt (~> 3.1.7) @@ -596,7 +595,6 @@ DEPENDENCIES capybara debug faker - fizzy-saas! geared_pagination (~> 1.2) image_processing (~> 1.14) importmap-rails @@ -612,10 +610,8 @@ DEPENDENCIES prometheus-client-mmap (~> 1.3) propshaft puma (>= 5.0) - queenbee! rack-mini-profiler rails! - rails_structured_logging! redcarpet rouge rqrcode diff --git a/Gemfile.saas b/Gemfile.saas new file mode 100644 index 0000000000..52f6197c4c --- /dev/null +++ b/Gemfile.saas @@ -0,0 +1,8 @@ +# This Gemfile extends the base Gemfile with SaaS-specific dependencies +eval_gemfile "Gemfile" + +gem "activeresource", require: "active_resource" +gem "queenbee", bc: "queenbee-plugin" +gem "fizzy-saas", bc: "fizzy-saas" +gem "rails_structured_logging", bc: "rails-structured-logging" + diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock new file mode 100644 index 0000000000..4a3483eb95 --- /dev/null +++ b/Gemfile.saas.lock @@ -0,0 +1,645 @@ +GIT + remote: https://github.com/basecamp/fizzy-saas + revision: 7efcfdc7d1b787aa5f023ea96656b9f67bcbfe7f + specs: + fizzy-saas (0.1.0) + queenbee + rails (>= 8.1.0.beta1) + rails_structured_logging + +GIT + remote: https://github.com/basecamp/queenbee-plugin + revision: 15faf03a876c5e66b67753d2e1ddb24f1eb5abb2 + specs: + queenbee (3.2.0) + activeresource + builder + rexml + +GIT + remote: https://github.com/basecamp/rails-structured-logging + revision: 76960cb5c15fc2b6b5f7542e05d7dcc031cef9e6 + specs: + rails_structured_logging (0.2.1) + json + rails (>= 6.0.0) + +GIT + remote: https://github.com/basecamp/yabeda-activejob.git + revision: 684973f77ff01d8b3dd75874538fae55961e15e6 + branch: bulk-and-scheduled-jobs + specs: + yabeda-activejob (0.6.0) + rails (>= 6.1) + yabeda (~> 0.6) + +GIT + remote: https://github.com/rails/rails.git + revision: 4f7ab01bb5d6be78c7447dbb230c55027d08ae34 + branch: main + specs: + actioncable (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + activejob (= 8.2.0.alpha) + activerecord (= 8.2.0.alpha) + activestorage (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + mail (>= 2.8.0) + actionmailer (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + actionview (= 8.2.0.alpha) + activejob (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.2.0.alpha) + actionview (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.2.0.alpha) + action_text-trix (~> 2.1.15) + actionpack (= 8.2.0.alpha) + activerecord (= 8.2.0.alpha) + activestorage (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.2.0.alpha) + activesupport (= 8.2.0.alpha) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.2.0.alpha) + activesupport (= 8.2.0.alpha) + globalid (>= 0.3.6) + activemodel (8.2.0.alpha) + activesupport (= 8.2.0.alpha) + activerecord (8.2.0.alpha) + activemodel (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + timeout (>= 0.4.0) + activestorage (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + activejob (= 8.2.0.alpha) + activerecord (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + marcel (~> 1.0) + activesupport (8.2.0.alpha) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + rails (8.2.0.alpha) + actioncable (= 8.2.0.alpha) + actionmailbox (= 8.2.0.alpha) + actionmailer (= 8.2.0.alpha) + actionpack (= 8.2.0.alpha) + actiontext (= 8.2.0.alpha) + actionview (= 8.2.0.alpha) + activejob (= 8.2.0.alpha) + activemodel (= 8.2.0.alpha) + activerecord (= 8.2.0.alpha) + activestorage (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + bundler (>= 1.15.0) + railties (= 8.2.0.alpha) + railties (8.2.0.alpha) + actionpack (= 8.2.0.alpha) + activesupport (= 8.2.0.alpha) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + +GEM + remote: https://rubygems.org/ + specs: + action_text-trix (2.1.15) + railties + activemodel-serializers-xml (1.0.3) + activemodel (>= 5.0.0.a) + activesupport (>= 5.0.0.a) + builder (~> 3.1) + activeresource (6.2.0) + activemodel (>= 7.0) + activemodel-serializers-xml (~> 1.0) + activesupport (>= 7.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + anyway_config (2.7.2) + ruby-next-core (~> 1.0) + ast (2.4.3) + autotuner (1.1.0) + aws-eventstream (1.4.0) + aws-partitions (1.1187.0) + aws-sdk-core (3.239.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.118.0) + aws-sdk-core (~> 3, >= 3.239.1) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.205.0) + aws-sdk-core (~> 3, >= 3.234.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + base64 (0.3.0) + bcrypt (3.1.20) + bcrypt_pbkdf (1.1.1) + benchmark (0.5.0) + bigdecimal (3.3.1) + bindex (0.8.1) + bootsnap (1.19.0) + msgpack (~> 1.2) + brakeman (7.1.1) + racc + builder (3.3.0) + bundler-audit (0.9.2) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) + capybara (3.40.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.11) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + childprocess (5.1.0) + logger (~> 1.5) + chunky_png (1.4.0) + concurrent-ruby (1.3.5) + connection_pool (2.5.4) + crack (1.0.1) + bigdecimal + rexml + crass (1.0.6) + date (3.5.0) + debug (1.11.0) + irb (~> 1.10) + reline (>= 0.3.8) + dotenv (3.1.8) + drb (2.2.3) + dry-initializer (3.2.0) + ed25519 (1.4.0) + erb (6.0.0) + erubi (1.13.1) + et-orbi (1.4.0) + tzinfo + faker (3.5.2) + i18n (>= 1.8.11, < 2) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + fugit (1.12.1) + et-orbi (~> 1.4) + raabro (~> 1.4) + geared_pagination (1.2.0) + activesupport (>= 5.0) + addressable (>= 2.5.0) + globalid (1.3.0) + activesupport (>= 6.1) + hashdiff (1.2.1) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + image_processing (1.14.0) + mini_magick (>= 4.9.5, < 6) + ruby-vips (>= 2.0.17, < 3) + importmap-rails (2.2.2) + actionpack (>= 6.0.0) + activesupport (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.3) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + jmespath (1.6.2) + json (2.16.0) + jwt (3.1.2) + base64 + kamal (2.8.2) + activesupport (>= 7.0) + base64 (~> 0.2) + bcrypt_pbkdf (~> 1.0) + concurrent-ruby (~> 1.2) + dotenv (~> 3.1) + ed25519 (~> 1.4) + net-ssh (~> 7.3) + sshkit (>= 1.23.0, < 2.0) + thor (~> 1.3) + zeitwerk (>= 2.6.18, < 3.0) + language_server-protocol (3.17.0.5) + launchy (3.1.1) + addressable (~> 2.8) + childprocess (~> 5.0) + logger (~> 1.6) + letter_opener (1.10.0) + launchy (>= 2.2, < 4) + lexxy (0.1.20.beta) + rails (>= 8.0.2) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + matrix (0.4.3) + mini_magick (5.3.1) + logger + mini_mime (1.1.5) + minitest (5.26.2) + mission_control-jobs (1.1.0) + actioncable (>= 7.1) + actionpack (>= 7.1) + activejob (>= 7.1) + activerecord (>= 7.1) + importmap-rails (>= 1.2.1) + irb (~> 1.13) + railties (>= 7.1) + stimulus-rails + turbo-rails + mittens (0.3.0) + mocha (2.8.2) + ruby2_keywords (>= 0.0.5) + msgpack (1.8.0) + net-http-persistent (4.0.6) + connection_pool (~> 2.2, >= 2.2.4) + net-imap (0.5.12) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-scp (4.1.0) + net-ssh (>= 2.6.5, < 8.0.0) + net-sftp (4.0.0) + net-ssh (>= 5.0.0, < 8.0.0) + net-smtp (0.5.1) + net-protocol + net-ssh (7.3.0) + nio4r (2.7.5) + nokogiri (1.18.10-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-musl) + racc (~> 1.4) + openssl (3.3.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + platform_agent (1.0.1) + activesupport (>= 5.2.0) + useragent (~> 0.16.3) + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.6.0) + prometheus-client-mmap (1.3.0) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + prometheus-client-mmap (1.3.0-aarch64-linux-gnu) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + prometheus-client-mmap (1.3.0-aarch64-linux-musl) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + prometheus-client-mmap (1.3.0-x86_64-linux-gnu) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + prometheus-client-mmap (1.3.0-x86_64-linux-musl) + base64 + bigdecimal + logger + rb_sys (~> 0.9.117) + propshaft (1.3.1) + actionpack (>= 7.0.0) + activesupport (>= 7.0.0) + rack + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (7.1.0) + nio4r (~> 2.0) + raabro (1.4.0) + racc (1.8.1) + rack (3.2.4) + rack-mini-profiler (4.0.1) + rack (>= 1.2.0) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + rainbow (3.1.1) + rake (13.3.1) + rake-compiler-dock (1.9.1) + rb_sys (0.9.117) + rake-compiler-dock (= 1.9.1) + rdoc (6.15.1) + erb + psych (>= 4.0.0) + tsort + redcarpet (3.6.1) + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + rexml (3.4.4) + rouge (4.6.1) + rqrcode (3.1.0) + chunky_png (~> 1.0) + rqrcode_core (~> 2.0) + rqrcode_core (2.0.0) + rubocop (1.81.7) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.47.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.48.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.0) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rails-omakase (1.1.0) + rubocop (>= 1.72) + rubocop-performance (>= 1.24) + rubocop-rails (>= 2.30) + ruby-next-core (1.1.2) + ruby-progressbar (1.13.0) + ruby-vips (2.2.5) + ffi (~> 1.12) + logger + ruby2_keywords (0.0.5) + rubyzip (3.2.2) + securerandom (0.4.1) + selenium-webdriver (4.38.0) + base64 (~> 0.2) + logger (~> 1.4) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 4.0) + websocket (~> 1.0) + sentry-rails (6.1.1) + railties (>= 5.2.0) + sentry-ruby (~> 6.1.1) + sentry-ruby (6.1.1) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + sniffer (0.5.0) + anyway_config (>= 1.0) + dry-initializer (~> 3) + solid_cable (3.0.12) + actioncable (>= 7.2) + activejob (>= 7.2) + activerecord (>= 7.2) + railties (>= 7.2) + solid_cache (1.0.10) + activejob (>= 7.2) + activerecord (>= 7.2) + railties (>= 7.2) + solid_queue (1.2.4) + activejob (>= 7.1) + activerecord (>= 7.1) + concurrent-ruby (>= 1.3.1) + fugit (~> 1.11) + railties (>= 7.1) + thor (>= 1.3.1) + sqlite3 (2.8.0-aarch64-linux-gnu) + sqlite3 (2.8.0-aarch64-linux-musl) + sqlite3 (2.8.0-arm-linux-gnu) + sqlite3 (2.8.0-arm-linux-musl) + sqlite3 (2.8.0-x86_64-linux-gnu) + sqlite3 (2.8.0-x86_64-linux-musl) + sshkit (1.24.0) + base64 + logger + net-scp (>= 1.1.2) + net-sftp (>= 2.1.2) + net-ssh (>= 2.8.0) + ostruct + stimulus-rails (1.3.4) + railties (>= 6.0.0) + stringio (3.1.8) + thor (1.4.0) + thruster (0.1.16) + thruster (0.1.16-aarch64-linux) + thruster (0.1.16-x86_64-linux) + timeout (0.4.4) + trilogy (2.9.0) + tsort (0.2.0) + turbo-rails (2.0.20) + actionpack (>= 7.1.0) + railties (>= 7.1.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) + uri (1.1.1) + useragent (0.16.11) + vcr (6.3.1) + base64 + web-console (4.2.1) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + web-push (3.0.2) + jwt (~> 3.0) + openssl (~> 3.0) + webmock (3.26.1) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + webrick (1.9.1) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yabeda (0.14.0) + anyway_config (>= 1.0, < 3) + concurrent-ruby + dry-initializer + yabeda-actioncable (0.2.2) + actioncable (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + yabeda (~> 0.8) + yabeda-gc (0.4.0) + yabeda (~> 0.6) + yabeda-http_requests (0.3.0) + anyway_config (>= 1.3, < 3.0) + sniffer + yabeda + yabeda-prometheus-mmap (0.4.0) + prometheus-client-mmap + yabeda (~> 0.10) + yabeda-puma-plugin (0.9.0) + json + puma + yabeda (~> 0.5) + yabeda-rails (0.10.0) + activesupport + anyway_config (>= 1.3, < 3) + railties + yabeda (~> 0.8) + zeitwerk (2.7.3) + +PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + activeresource + autotuner + aws-sdk-s3 + bcrypt (~> 3.1.7) + benchmark + bootsnap + brakeman + bundler-audit + capybara + debug + faker + fizzy-saas! + geared_pagination (~> 1.2) + image_processing (~> 1.14) + importmap-rails + jbuilder + kamal + letter_opener + lexxy + mission_control-jobs + mittens + mocha + net-http-persistent + platform_agent + prometheus-client-mmap (~> 1.1) + propshaft + puma (>= 5.0) + queenbee! + rack-mini-profiler + rails! + rails_structured_logging! + redcarpet + rouge + rqrcode + rubocop-rails-omakase + selenium-webdriver + sentry-rails + sentry-ruby + solid_cable (>= 3.0) + solid_cache (~> 1.0) + solid_queue (~> 1.2) + sqlite3 (>= 2.0) + stimulus-rails + thruster + trilogy (~> 2.9) + turbo-rails + vcr + web-console + web-push + webmock + webrick + yabeda + yabeda-actioncable + yabeda-activejob! + yabeda-gc + yabeda-http_requests + yabeda-prometheus-mmap + yabeda-puma-plugin + yabeda-rails + +BUNDLED WITH + 2.7.2 diff --git a/lib/fizzy.rb b/lib/fizzy.rb index 614add8ecb..616f5bef19 100644 --- a/lib/fizzy.rb +++ b/lib/fizzy.rb @@ -11,7 +11,7 @@ def db_adapter def configure_bundle if saas? - ENV["BUNDLE_WITH"] = [ ENV["BUNDLE_WITH"], "saas" ].compact.join(",") + ENV["BUNDLE_GEMFILE"] = "Gemfile.saas" end end end From e3f2fcb206e3e0d782a29435ece31d2a64866d01 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:00:21 +0100 Subject: [PATCH 19/49] Prepare bin/setup to work with SAAS env variable / txt file --- bin/setup | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/bin/setup b/bin/setup index 1c9c6ac61f..74372114a0 100755 --- a/bin/setup +++ b/bin/setup @@ -8,6 +8,14 @@ app_root="$( )" export PATH="$app_root/bin:$PATH" +if [ -e tmp/saas.txt ]; then + export SAAS=1 +fi + +if [ -n "$SAAS" ]; then + export BUNDLE_GEMFILE="Gemfile.saas" +fi + # Install gum if needed if ! command -v gum &>/dev/null; then echo @@ -71,14 +79,15 @@ step "Set up gh-signoff" bash -c "gh extension install basecamp/gh-signoff || gh bundle config set --local auto_install true step "Installing RubyGems" bundle install -if [ -e tmp/minio-dev.txt ]; then - step "Starting Docker services" bash -c "[ -d ~/Work/basecamp/docker-dev ] && git -C ~/Work/basecamp/docker-dev pull || gh repo clone basecamp/docker-dev ~/Work/basecamp/docker-dev && ~/Work/basecamp/docker-dev/setup minio" +if [ -n "$SAAS" ]; then + if [ -e tmp/minio-dev.txt ]; then + step "Starting Docker services" bash -c "[ -d ~/Work/basecamp/docker-dev ] && git -C ~/Work/basecamp/docker-dev pull || gh repo clone basecamp/docker-dev ~/Work/basecamp/docker-dev && ~/Work/basecamp/docker-dev/setup minio" + step "Configuring MinIO" bin/minio-setup + fi - step "Configuring MinIO" bin/minio-setup + step "Starting mysql" bash -c "[ -d ~/Work/basecamp/docker-dev ] && git -C ~/Work/basecamp/docker-dev pull || gh repo clone basecamp/docker-dev ~/Work/basecamp/docker-dev && ~/Work/basecamp/docker-dev/setup mysql80" fi -step "Starting mysql" bash -c "[ -d ~/Work/basecamp/docker-dev ] && git -C ~/Work/basecamp/docker-dev pull || gh repo clone basecamp/docker-dev ~/Work/basecamp/docker-dev && ~/Work/basecamp/docker-dev/setup mysql80" - if [[ $* == *--reset* ]]; then step "Resetting the database" rails db:reset else From 2ae1c58dc5b1bcf5455567e2812ce5e1c6fa4010 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:12:33 +0100 Subject: [PATCH 20/49] Remove line, this gets bin/setup confused, better to keep the rails output clean --- bin/rails | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/rails b/bin/rails index e644775f8f..9023575415 100755 --- a/bin/rails +++ b/bin/rails @@ -6,7 +6,5 @@ Fizzy.configure_bundle require_relative "../config/boot" -puts "SaaS version (#{Fizzy.db_adapter})" if Fizzy.saas? - require "rails/commands" From ff1ca1c3238c5c3b9a612bc380bc4c476add49a2 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:22:32 +0100 Subject: [PATCH 21/49] Invoke setup script in gem --- Gemfile.saas | 2 +- Gemfile.saas.lock | 17 ++++++++--------- bin/setup | 8 ++------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Gemfile.saas b/Gemfile.saas index 52f6197c4c..81ff0abb60 100644 --- a/Gemfile.saas +++ b/Gemfile.saas @@ -3,6 +3,6 @@ eval_gemfile "Gemfile" gem "activeresource", require: "active_resource" gem "queenbee", bc: "queenbee-plugin" -gem "fizzy-saas", bc: "fizzy-saas" +gem "fizzy-saas", path: "/home/jorge/Work/basecamp/fizzy-saas" gem "rails_structured_logging", bc: "rails-structured-logging" diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 4a3483eb95..0a9643f3e8 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -1,12 +1,3 @@ -GIT - remote: https://github.com/basecamp/fizzy-saas - revision: 7efcfdc7d1b787aa5f023ea96656b9f67bcbfe7f - specs: - fizzy-saas (0.1.0) - queenbee - rails (>= 8.1.0.beta1) - rails_structured_logging - GIT remote: https://github.com/basecamp/queenbee-plugin revision: 15faf03a876c5e66b67753d2e1ddb24f1eb5abb2 @@ -134,6 +125,14 @@ GIT tsort (>= 0.2) zeitwerk (~> 2.6) +PATH + remote: ../fizzy-saas + specs: + fizzy-saas (0.1.0) + queenbee + rails (>= 8.1.0.beta1) + rails_structured_logging + GEM remote: https://rubygems.org/ specs: diff --git a/bin/setup b/bin/setup index 74372114a0..e47bc5a939 100755 --- a/bin/setup +++ b/bin/setup @@ -80,12 +80,8 @@ bundle config set --local auto_install true step "Installing RubyGems" bundle install if [ -n "$SAAS" ]; then - if [ -e tmp/minio-dev.txt ]; then - step "Starting Docker services" bash -c "[ -d ~/Work/basecamp/docker-dev ] && git -C ~/Work/basecamp/docker-dev pull || gh repo clone basecamp/docker-dev ~/Work/basecamp/docker-dev && ~/Work/basecamp/docker-dev/setup minio" - step "Configuring MinIO" bin/minio-setup - fi - - step "Starting mysql" bash -c "[ -d ~/Work/basecamp/docker-dev ] && git -C ~/Work/basecamp/docker-dev pull || gh repo clone basecamp/docker-dev ~/Work/basecamp/docker-dev && ~/Work/basecamp/docker-dev/setup mysql80" + saas_setup=$(bundle show fizzy-saas)/bin/setup + source "$saas_setup" fi if [[ $* == *--reset* ]]; then From 1337a963dcf971c91b2b5d30ecb82ccd32c2e908 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:34:54 +0100 Subject: [PATCH 22/49] Format --- config/ci.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/ci.rb b/config/ci.rb index df7f3aed2e..1175427dd1 100644 --- a/config/ci.rb +++ b/config/ci.rb @@ -11,9 +11,9 @@ step "Security: Importmap audit", "bin/importmap audit" step "Security: Brakeman audit", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error" - step "Tests: Fizzy", "bin/rails test" + step "Tests: Fizzy", "bin/rails test" - step "Tests: SaaS", "SAAS=1 bin/rails test:saas" if Fizzy.saas? + step "Tests: SaaS", "SAAS=1 bin/rails test:saas" if Fizzy.saas? step "Tests: System", "bin/rails test:system" if success? From 2b638efefbb6e187f40878255bbebb351e19fe77 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:35:26 +0100 Subject: [PATCH 23/49] Format --- config/ci.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/ci.rb b/config/ci.rb index 1175427dd1..527d7d8cb3 100644 --- a/config/ci.rb +++ b/config/ci.rb @@ -12,8 +12,8 @@ step "Security: Brakeman audit", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error" step "Tests: Fizzy", "bin/rails test" - step "Tests: SaaS", "SAAS=1 bin/rails test:saas" if Fizzy.saas? + step "Tests: System", "bin/rails test:system" if success? From 76c394b53ae065aa577b2fa63a9a9bb7a542dae4 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:36:47 +0100 Subject: [PATCH 24/49] Not needed anymore, no more bundler groups --- config/application.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/config/application.rb b/config/application.rb index 4bfbab4a22..0399caacac 100644 --- a/config/application.rb +++ b/config/application.rb @@ -2,9 +2,7 @@ require "rails/all" require_relative "../lib/fizzy" -groups = Rails.groups -groups << :saas if Fizzy.saas? -Bundler.require(*groups) +Bundler.require(*Rails.groups) module Fizzy class Application < Rails::Application From 140289c6cca09adabc5781eebb89a62b56125354 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:44:47 +0100 Subject: [PATCH 25/49] Bring vanilla versions for Dockerfile and deploy config, we are moving those to the private saas gem --- Dockerfile | 81 ++++++++++++++++--------------- config/deploy.yml | 120 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 41 deletions(-) create mode 100644 config/deploy.yml diff --git a/Dockerfile b/Dockerfile index 3f272de4e8..3cd25c7f19 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,77 +1,76 @@ +# syntax=docker/dockerfile:1 +# check=error=true + +# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: +# docker build -t fizzy . +# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name fizzy fizzy + +# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html + # Make sure RUBY_VERSION matches the Ruby version in .ruby-version ARG RUBY_VERSION=3.4.7 -FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim AS base +FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base # Rails app lives here WORKDIR /rails -# Set production environment +# Install base packages +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \ + ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Set production environment variables and enable jemalloc for reduced memory usage and latency. ENV RAILS_ENV="production" \ BUNDLE_DEPLOYMENT="1" \ BUNDLE_PATH="/usr/local/bundle" \ - BUNDLE_WITHOUT="development" - + BUNDLE_WITHOUT="development" \ + LD_PRELOAD="/usr/local/lib/libjemalloc.so" # Throw-away build stage to reduce size of final image FROM base AS build # Install packages needed to build gems RUN apt-get update -qq && \ - apt-get install -y --no-install-recommends -y build-essential pkg-config git libvips libyaml-dev libssl-dev && \ + apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # Install application gems -COPY Gemfile Gemfile.lock .ruby-version ./ -COPY lib/fizzy.rb ./lib/fizzy.rb -COPY gems ./gems/ -RUN --mount=type=secret,id=GITHUB_TOKEN --mount=type=cache,id=fizzy-permabundle-${RUBY_VERSION},sharing=locked,target=/permabundle \ - gem install bundler && \ - BUNDLE_PATH=/permabundle BUNDLE_GITHUB__COM="$(cat /run/secrets/GITHUB_TOKEN):x-oauth-basic" bundle install && \ - cp -a /permabundle/. "$BUNDLE_PATH"/ && \ - bundle clean --force && \ - rm -rf "$BUNDLE_PATH"/ruby/*/bundler/gems/*/.git && \ - find "$BUNDLE_PATH" -type f \( -name '*.gem' -o -iname '*.a' -o -iname '*.o' -o -iname '*.h' -o -iname '*.c' -o -iname '*.hpp' -o -iname '*.cpp' \) -delete && \ - bundle exec bootsnap precompile --gemfile +COPY Gemfile Gemfile.lock vendor ./ + +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + # -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495 + bundle exec bootsnap precompile -j 1 --gemfile # Copy application code COPY . . -# Precompile bootsnap code for faster boot times -RUN bundle exec bootsnap precompile app/ lib/ +# Precompile bootsnap code for faster boot times. +# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495 +RUN bundle exec bootsnap precompile -j 1 app/ lib/ # Precompiling assets for production without requiring secret RAILS_MASTER_KEY RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile + + + # Final stage for app image FROM base -# Install packages needed for deployment -RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libsqlite3-0 libvips build-essential ffmpeg groff libreoffice-writer libreoffice-impress libreoffice-calc mupdf-tools sqlite3 libjemalloc-dev && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives +# Run and own only the runtime files as a non-root user for security +RUN groupadd --system --gid 1000 rails && \ + useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash +USER 1000:1000 # Copy built artifacts: gems, application -COPY --from=build /usr/local/bundle /usr/local/bundle -COPY --from=build /rails /rails - -# Run and own only the runtime files as a non-root user for security -RUN useradd rails --create-home --shell /bin/bash && \ - chown -R rails:rails db log storage tmp -USER rails:rails +COPY --chown=rails:rails --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" +COPY --chown=rails:rails --from=build /rails /rails # Entrypoint prepares the database. ENTRYPOINT ["/rails/bin/docker-entrypoint"] -# Ruby GC tuning values pulled from Autotuner recommendations -ENV RUBY_GC_HEAP_0_INIT_SLOTS=692636 \ - RUBY_GC_HEAP_1_INIT_SLOTS=175943 \ - RUBY_GC_HEAP_2_INIT_SLOTS=148807 \ - RUBY_GC_HEAP_3_INIT_SLOTS=9169 \ - RUBY_GC_HEAP_4_INIT_SLOTS=3054 \ - RUBY_GC_MALLOC_LIMIT=33554432 \ - RUBY_GC_MALLOC_LIMIT_MAX=67108864 \ - LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 - -# Start the server by default, this can be overwritten at runtime -EXPOSE 80 443 9394 +# Start server via Thruster by default, this can be overwritten at runtime +EXPOSE 80 CMD ["./bin/thrust", "./bin/rails", "server"] diff --git a/config/deploy.yml b/config/deploy.yml new file mode 100644 index 0000000000..be1e3c21e6 --- /dev/null +++ b/config/deploy.yml @@ -0,0 +1,120 @@ +# Name of your application. Used to uniquely configure containers. +service: fizzy + +# Name of the container image (use your-user/app-name on external registries). +image: fizzy + +# Deploy to these servers. +servers: + web: + - 192.168.0.1 + # job: + # hosts: + # - 192.168.0.1 + # cmd: bin/jobs + +# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server. +# If used with Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption. +# +# Using an SSL proxy like this requires turning on config.assume_ssl and config.force_ssl in production.rb! +# +# Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer). +# +# proxy: +# ssl: true +# host: app.example.com + +# Where you keep your container images. +registry: + # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ... + server: localhost:5555 + + # Needed for authenticated registries. + # username: your-user + + # Always use an access token rather than real password when possible. + # password: + # - KAMAL_REGISTRY_PASSWORD + +# Inject ENV variables into containers (secrets come from .kamal/secrets). +env: + secret: + - RAILS_MASTER_KEY + clear: + # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. + # When you start using multiple servers, you should split out job processing to a dedicated machine. + SOLID_QUEUE_IN_PUMA: true + + # Set number of processes dedicated to Solid Queue (default: 1) + # JOB_CONCURRENCY: 3 + + # Set number of cores available to the application on each server (default: 1). + # WEB_CONCURRENCY: 2 + + # Match this to any external database server to configure Active Record correctly + # Use fizzy-db for a db accessory server on same machine via local kamal docker network. + # DB_HOST: 192.168.0.2 + + # Log everything from Rails + # RAILS_LOG_LEVEL: debug + +# Aliases are triggered with "bin/kamal ". You can overwrite arguments on invocation: +# "bin/kamal logs -r job" will tail logs from the first server in the job section. +aliases: + console: app exec --interactive --reuse "bin/rails console" + shell: app exec --interactive --reuse "bash" + logs: app logs -f + dbc: app exec --interactive --reuse "bin/rails dbconsole --include-password" + +# Use a persistent storage volume for sqlite database files and local Active Storage files. +# Recommended to change this to a mounted volume path that is backed up off server. +volumes: + - "fizzy_storage:/rails/storage" + +# Bridge fingerprinted assets, like JS and CSS, between versions to avoid +# hitting 404 on in-flight requests. Combines all files from new and old +# version inside the asset_path. +asset_path: /rails/public/assets + + +# Configure the image builder. +builder: + arch: amd64 + + # # Build image via remote server (useful for faster amd64 builds on arm64 computers) + # remote: ssh://docker@docker-builder-server + # + # # Pass arguments and secrets to the Docker build process + # args: + # RUBY_VERSION: ruby-3.4.7 + # secrets: + # - GITHUB_TOKEN + # - RAILS_MASTER_KEY + +# Use a different ssh user than root +# ssh: +# user: app + +# Use accessory services (secrets come from .kamal/secrets). +# accessories: +# db: +# image: mysql:8.0 +# host: 192.168.0.2 +# # Change to 3306 to expose port to the world instead of just local network. +# port: "127.0.0.1:3306:3306" +# env: +# clear: +# MYSQL_ROOT_HOST: '%' +# secret: +# - MYSQL_ROOT_PASSWORD +# files: +# - config/mysql/production.cnf:/etc/mysql/my.cnf +# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql +# directories: +# - data:/var/lib/mysql +# redis: +# image: valkey/valkey:8 +# host: 192.168.0.2 +# port: 6379 +# directories: +# - data:/data From e4d53d1f72ad80882892440e5aa63bf322e5fdba Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:48:51 +0100 Subject: [PATCH 26/49] Point to the remote gem again --- Gemfile.saas | 2 +- Gemfile.saas.lock | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Gemfile.saas b/Gemfile.saas index 81ff0abb60..52f6197c4c 100644 --- a/Gemfile.saas +++ b/Gemfile.saas @@ -3,6 +3,6 @@ eval_gemfile "Gemfile" gem "activeresource", require: "active_resource" gem "queenbee", bc: "queenbee-plugin" -gem "fizzy-saas", path: "/home/jorge/Work/basecamp/fizzy-saas" +gem "fizzy-saas", bc: "fizzy-saas" gem "rails_structured_logging", bc: "rails-structured-logging" diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 0a9643f3e8..4957ae875e 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/basecamp/fizzy-saas + revision: 38562580daf4d2f4d5f582411086ddf14aaa77a9 + specs: + fizzy-saas (0.1.0) + queenbee + rails (>= 8.1.0.beta1) + rails_structured_logging + GIT remote: https://github.com/basecamp/queenbee-plugin revision: 15faf03a876c5e66b67753d2e1ddb24f1eb5abb2 @@ -125,14 +134,6 @@ GIT tsort (>= 0.2) zeitwerk (~> 2.6) -PATH - remote: ../fizzy-saas - specs: - fizzy-saas (0.1.0) - queenbee - rails (>= 8.1.0.beta1) - rails_structured_logging - GEM remote: https://rubygems.org/ specs: From 9aa024a70d4cb47ee1508e47a2f919bd71b4d583 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:51:25 +0100 Subject: [PATCH 27/49] Update fizzy-saas --- Gemfile.saas.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 4957ae875e..814a9a63cd 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/basecamp/fizzy-saas - revision: 38562580daf4d2f4d5f582411086ddf14aaa77a9 + revision: cb340c7f3b442b904379524de43275e3e0950064 specs: fizzy-saas (0.1.0) queenbee From 83abdd39328a2ddb4c012a306414534f0938a46d Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 18:57:32 +0100 Subject: [PATCH 28/49] Fix path to saas. --- lib/fizzy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fizzy.rb b/lib/fizzy.rb index 616f5bef19..69b3f9c6f2 100644 --- a/lib/fizzy.rb +++ b/lib/fizzy.rb @@ -2,7 +2,7 @@ module Fizzy class << self def saas? return @saas if defined?(@saas) - @saas = !!(ENV["SAAS"] || File.exist?(File.expand_path("../../tmp/saas.txt", __dir__))) + @saas = !!(ENV["SAAS"] || File.exist?(File.expand_path("../tmp/saas.txt", __dir__))) end def db_adapter From 7c1b1b73b8cea7447c3e2b9accf403989b8c225b Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 19:11:14 +0100 Subject: [PATCH 29/49] Update to latest fizzy-saas --- Gemfile.saas.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 814a9a63cd..120a939b11 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/basecamp/fizzy-saas - revision: cb340c7f3b442b904379524de43275e3e0950064 + revision: 7b096f10526ac797ec9e14314a4752c1482c9152 specs: fizzy-saas (0.1.0) queenbee From 49e2e2fb39afe917fc0a5840a4c4740a24175f38 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 23 Nov 2025 20:26:03 +0100 Subject: [PATCH 30/49] Add rake tasks for enabling/disabling saas mode --- Gemfile.saas.lock | 2 +- bin/rails | 1 - lib/tasks/saas.rake | 18 ++++++++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 lib/tasks/saas.rake diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 120a939b11..7fdfc8bac8 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/basecamp/fizzy-saas - revision: 7b096f10526ac797ec9e14314a4752c1482c9152 + revision: f901a455ecc8c313bdba7be89619d289fa7e112c specs: fizzy-saas (0.1.0) queenbee diff --git a/bin/rails b/bin/rails index 9023575415..9d935530de 100755 --- a/bin/rails +++ b/bin/rails @@ -7,4 +7,3 @@ Fizzy.configure_bundle require_relative "../config/boot" require "rails/commands" - diff --git a/lib/tasks/saas.rake b/lib/tasks/saas.rake new file mode 100644 index 0000000000..77df8f4215 --- /dev/null +++ b/lib/tasks/saas.rake @@ -0,0 +1,18 @@ +namespace :saas do + SAAS_FILE_PATH = "tmp/saas.txt" + + desc "Enable SaaS mode" + task :enable => :environment do + file_path = Rails.root.join(SAAS_FILE_PATH) + FileUtils.mkdir_p(File.dirname(file_path)) + FileUtils.touch(file_path) + puts "SaaS mode enabled (#{file_path} created)" + end + + desc "Disable SaaS mode" + task :disable => :environment do + file_path = Rails.root.join(SAAS_FILE_PATH) + FileUtils.rm_f(file_path) + puts "SaaS mode disabled (#{file_path} removed)" + end +end From 5c35bdcfbd0d6eb0fc18a32e16cb099ebfc836ee Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Mon, 24 Nov 2025 08:36:10 +0100 Subject: [PATCH 31/49] Move bc shorthand to saas gemfile --- Gemfile | 1 - Gemfile.saas | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index a4cb0a29fe..e4e7bf772b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,4 @@ source "https://rubygems.org" -git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } gem "rails", github: "rails/rails", branch: "main" diff --git a/Gemfile.saas b/Gemfile.saas index 52f6197c4c..779c8c4198 100644 --- a/Gemfile.saas +++ b/Gemfile.saas @@ -1,6 +1,9 @@ # This Gemfile extends the base Gemfile with SaaS-specific dependencies eval_gemfile "Gemfile" +git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } + + gem "activeresource", require: "active_resource" gem "queenbee", bc: "queenbee-plugin" gem "fizzy-saas", bc: "fizzy-saas" From 11623337dbca2be2fcac366471ba9470ea227f8a Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Mon, 24 Nov 2025 15:43:16 +0100 Subject: [PATCH 32/49] Test moved to the gem --- test/controllers/sessions_controller_test.rb | 21 -------------------- 1 file changed, 21 deletions(-) diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 7fed629008..2f5b27b0dc 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -21,22 +21,6 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest end end - if Fizzy.saas? - test "create for a new user" do - untenanted do - assert_difference -> { Identity.count }, +1 do - assert_difference -> { MagicLink.count }, +1 do - post session_path, - params: { email_address: "nonexistent-#{SecureRandom.hex(6)}@example.com" }, - headers: http_basic_auth_headers("testname", "testpassword") - end - end - - assert_redirected_to session_magic_link_path - end - end - end - test "destroy" do sign_in_as :kevin @@ -47,9 +31,4 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest assert_not cookies[:session_token].present? end end - - private - def http_basic_auth_headers(user, password) - { "Authorization" => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) } - end end From 0b92e632e45fe932b785e7bbc435cae680efe4fa Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Mon, 24 Nov 2025 16:27:17 +0100 Subject: [PATCH 33/49] Update to last version, tests passing! --- Gemfile.saas.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 7fdfc8bac8..1697468f67 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/basecamp/fizzy-saas - revision: f901a455ecc8c313bdba7be89619d289fa7e112c + revision: 8b096fa8cb4c2823a726a0dae872214ac3642a34 specs: fizzy-saas (0.1.0) queenbee From 4e8e9f93f4dfac5bfc27c879253cff8844f9c92e Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Mon, 24 Nov 2025 16:37:20 +0100 Subject: [PATCH 34/49] Initial github action setup --- .github/workflows/ci.yml | 100 +++++++++++++++++++++++++++++++++++++++ lib/tasks/saas.rake | 4 +- 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..6bd2efb26d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,100 @@ +name: CI + +on: + push: + pull_request: + +jobs: + security: + name: Security + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@v1 + env: + BUNDLE_GITHUB__COM: "x-access-token:${{ secrets.GH_TOKEN }}" + BUNDLE_GEMFILE: Gemfile + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Gem audit + run: bin/bundler-audit check --update + + - name: Importmap audit + run: bin/importmap audit + + - name: Brakeman audit + run: bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error + + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@v1 + env: + BUNDLE_GITHUB__COM: "x-access-token:${{ secrets.GH_TOKEN }}" + BUNDLE_GEMFILE: Gemfile + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Lint code for consistent style + run: bin/rubocop + + + test: + name: Tests (${{ matrix.mode }}) + runs-on: ubuntu-latest + + strategy: + matrix: + mode: [SQLite, MySQL, SaaS] + + services: + mysql: + image: ${{ (matrix.mode == 'MySQL' || matrix.mode == 'SaaS') && 'mysql:8.0' || '' }} + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: fizzy_test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + env: + RAILS_ENV: test + DATABASE_ADAPTER: ${{ matrix.mode == 'SQLite' && 'sqlite' || 'mysql' }} + ${{ matrix.mode == 'SaaS' && 'SAAS' || 'SAAS_DISABLED' }}: ${{ matrix.mode == 'SaaS' && '1' || '' }} + BUNDLE_GEMFILE: ${{ matrix.mode == 'SaaS' && 'Gemfile.saas' || 'Gemfile' }} + MYSQL_HOST: 127.0.0.1 + MYSQL_PORT: 3306 + MYSQL_USER: root + + steps: + - name: Install system packages + run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libsqlite3-0 libvips curl ffmpeg + + - uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@v1 + env: + BUNDLE_GITHUB__COM: "x-access-token:${{ secrets.GH_TOKEN }}" + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run tests + run: bin/rails db:setup test + + - name: Run system tests + run: bin/rails test:system diff --git a/lib/tasks/saas.rake b/lib/tasks/saas.rake index 77df8f4215..4318da05a4 100644 --- a/lib/tasks/saas.rake +++ b/lib/tasks/saas.rake @@ -2,7 +2,7 @@ namespace :saas do SAAS_FILE_PATH = "tmp/saas.txt" desc "Enable SaaS mode" - task :enable => :environment do + task enable: :environment do file_path = Rails.root.join(SAAS_FILE_PATH) FileUtils.mkdir_p(File.dirname(file_path)) FileUtils.touch(file_path) @@ -10,7 +10,7 @@ namespace :saas do end desc "Disable SaaS mode" - task :disable => :environment do + task disable: :environment do file_path = Rails.root.join(SAAS_FILE_PATH) FileUtils.rm_f(file_path) puts "SaaS mode disabled (#{file_path} removed)" From cef611a0924a46bed34780e7b69dee7e6add49a6 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 12:12:56 +0100 Subject: [PATCH 35/49] Omit empty image --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bd2efb26d..17c2020cb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: services: mysql: - image: ${{ (matrix.mode == 'MySQL' || matrix.mode == 'SaaS') && 'mysql:8.0' || '' }} + image: 'mysql:8.0' env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: fizzy_test From ed4efc4b2d5bab266a3a69680ed70db0770ea867 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 12:15:56 +0100 Subject: [PATCH 36/49] Omit MySQL service with if condition --- .github/workflows/ci.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17c2020cb4..6c26b49b6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,11 +55,18 @@ jobs: strategy: matrix: - mode: [SQLite, MySQL, SaaS] + include: + - mode: SQLite + db_adapter: sqlite + - mode: MySQL + db_adapter: mysql + - mode: SaaS + db_adapter: mysql services: mysql: - image: 'mysql:8.0' + if: ${{ matrix.db_adapter == 'mysql' }} + image: mysql:8.0 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: fizzy_test @@ -73,7 +80,7 @@ jobs: env: RAILS_ENV: test - DATABASE_ADAPTER: ${{ matrix.mode == 'SQLite' && 'sqlite' || 'mysql' }} + DATABASE_ADAPTER: ${{ matrix.db_adapter }} ${{ matrix.mode == 'SaaS' && 'SAAS' || 'SAAS_DISABLED' }}: ${{ matrix.mode == 'SaaS' && '1' || '' }} BUNDLE_GEMFILE: ${{ matrix.mode == 'SaaS' && 'Gemfile.saas' || 'Gemfile' }} MYSQL_HOST: 127.0.0.1 From 95e045dae02c95d142d27c423d9e7f099df647b4 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 12:18:36 +0100 Subject: [PATCH 37/49] LOL if does not exist --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c26b49b6e..1ebe6a84bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,6 @@ jobs: services: mysql: - if: ${{ matrix.db_adapter == 'mysql' }} image: mysql:8.0 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes From b7c69786e14492dce1648fe9fce55b97e0f1165c Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 12:22:27 +0100 Subject: [PATCH 38/49] Try to pin the host --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ebe6a84bc..0e1cda9e5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: DATABASE_ADAPTER: ${{ matrix.db_adapter }} ${{ matrix.mode == 'SaaS' && 'SAAS' || 'SAAS_DISABLED' }}: ${{ matrix.mode == 'SaaS' && '1' || '' }} BUNDLE_GEMFILE: ${{ matrix.mode == 'SaaS' && 'Gemfile.saas' || 'Gemfile' }} - MYSQL_HOST: 127.0.0.1 + MYSQL_HOST: mysql MYSQL_PORT: 3306 MYSQL_USER: root From 6aa2a292757482d62224ae20987a893e5852e69d Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 12:31:23 +0100 Subject: [PATCH 39/49] Set proper SaaS vars for mysql --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e1cda9e5e..d877e78793 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,8 @@ jobs: MYSQL_HOST: mysql MYSQL_PORT: 3306 MYSQL_USER: root + FIZZY_DB_HOST: ${{ matrix.mode == 'SaaS' && 'mysql' || '' }} + FIZZY_DB_PORT: ${{ matrix.mode == 'SaaS' && '3306' || '' }} steps: - name: Install system packages From a24895aadd388e65636891993177138e21217e24 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 12:34:19 +0100 Subject: [PATCH 40/49] Fix hostname --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d877e78793..f7e211a9c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,10 +82,10 @@ jobs: DATABASE_ADAPTER: ${{ matrix.db_adapter }} ${{ matrix.mode == 'SaaS' && 'SAAS' || 'SAAS_DISABLED' }}: ${{ matrix.mode == 'SaaS' && '1' || '' }} BUNDLE_GEMFILE: ${{ matrix.mode == 'SaaS' && 'Gemfile.saas' || 'Gemfile' }} - MYSQL_HOST: mysql + MYSQL_HOST: 127.0.0.1 MYSQL_PORT: 3306 MYSQL_USER: root - FIZZY_DB_HOST: ${{ matrix.mode == 'SaaS' && 'mysql' || '' }} + FIZZY_DB_HOST: ${{ matrix.mode == 'SaaS' && '127.0.0.1' || '' }} FIZZY_DB_PORT: ${{ matrix.mode == 'SaaS' && '3306' || '' }} steps: From 325738256140aecae6e03987dcb74b38f9931de7 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Wed, 26 Nov 2025 11:52:11 +0100 Subject: [PATCH 41/49] Bundle! --- Gemfile.lock | 34 ---------------------------------- Gemfile.saas.lock | 10 ++++++++-- 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6556fd3320..ded3d30825 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,3 @@ -GIT - remote: https://github.com/basecamp/fizzy-saas - revision: 00de8e2e1ae8e9df5e0a2acf4593a4e9a824c5c8 - specs: - fizzy-saas (0.1.0) - queenbee - rails (>= 8.1.0.beta1) - rails_structured_logging - -GIT - remote: https://github.com/basecamp/queenbee-plugin - revision: 15faf03a876c5e66b67753d2e1ddb24f1eb5abb2 - specs: - queenbee (3.2.0) - activeresource - builder - rexml - -GIT - remote: https://github.com/basecamp/rails-structured-logging - revision: 76960cb5c15fc2b6b5f7542e05d7dcc031cef9e6 - specs: - rails_structured_logging (0.2.1) - json - rails (>= 6.0.0) - GIT remote: https://github.com/basecamp/useragent revision: 433ca320a42db1266c4b89df74d0abdb9a880c5e @@ -145,14 +119,6 @@ GEM specs: action_text-trix (2.1.15) railties - activemodel-serializers-xml (1.0.3) - activemodel (>= 5.0.0.a) - activesupport (>= 5.0.0.a) - builder (~> 3.1) - activeresource (6.2.0) - activemodel (>= 7.0) - activemodel-serializers-xml (~> 1.0) - activesupport (>= 7.0) addressable (2.8.8) public_suffix (>= 2.0.2, < 8.0) anyway_config (2.7.2) diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 1697468f67..21825b4b9a 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -24,6 +24,12 @@ GIT json rails (>= 6.0.0) +GIT + remote: https://github.com/basecamp/useragent + revision: 433ca320a42db1266c4b89df74d0abdb9a880c5e + specs: + useragent (0.16.11) + GIT remote: https://github.com/basecamp/yabeda-activejob.git revision: 684973f77ff01d8b3dd75874538fae55961e15e6 @@ -519,7 +525,6 @@ GEM unicode-emoji (~> 4.1) unicode-emoji (4.1.0) uri (1.1.1) - useragent (0.16.11) vcr (6.3.1) base64 web-console (4.2.1) @@ -605,7 +610,7 @@ DEPENDENCIES mocha net-http-persistent platform_agent - prometheus-client-mmap (~> 1.1) + prometheus-client-mmap (~> 1.3) propshaft puma (>= 5.0) queenbee! @@ -627,6 +632,7 @@ DEPENDENCIES thruster trilogy (~> 2.9) turbo-rails + useragent! vcr web-console web-push From aa913640c5bca8c419afe4c1b52d1ccee5a2404e Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Wed, 26 Nov 2025 11:48:28 +0100 Subject: [PATCH 42/49] Restore bc shorthand --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index e4e7bf772b..ce2324e1b3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source "https://rubygems.org" gem "rails", github: "rails/rails", branch: "main" +git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } # Assets & front end gem "importmap-rails" From 4c8abe675e383c84e6a5b89b06380174dc9edecf Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Wed, 26 Nov 2025 12:01:44 +0100 Subject: [PATCH 43/49] Bring changes from main --- config/database.sqlite.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/config/database.sqlite.yml b/config/database.sqlite.yml index 44fcda8cc9..7cc7404c7f 100644 --- a/config/database.sqlite.yml +++ b/config/database.sqlite.yml @@ -1,21 +1,22 @@ default: &default adapter: sqlite3 - pool: 50 + pool: 5 timeout: 5000 development: primary: <<: *default - database: db/development.sqlite3 + database: storage/development.sqlite3 schema_dump: schema_sqlite.rb test: primary: <<: *default - database: db/test.sqlite3 + database: storage/test.sqlite3 schema_dump: schema_sqlite.rb production: primary: <<: *default - database: db/production.sqlite3 + database: storage/production.sqlite3 + schema_dump: schema_sqlite.rb From 19bf03d731742e5e66837878750d1fccc1506437 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Wed, 26 Nov 2025 12:31:39 +0100 Subject: [PATCH 44/49] Fix condition --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7e211a9c2..8b54f59b4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,8 +80,8 @@ jobs: env: RAILS_ENV: test DATABASE_ADAPTER: ${{ matrix.db_adapter }} - ${{ matrix.mode == 'SaaS' && 'SAAS' || 'SAAS_DISABLED' }}: ${{ matrix.mode == 'SaaS' && '1' || '' }} - BUNDLE_GEMFILE: ${{ matrix.mode == 'SaaS' && 'Gemfile.saas' || 'Gemfile' }} + ${{ inputs.saas && 'SAAS' || 'SAAS_DISABLED' }}: ${{ inputs.saas && '1' || '' }} + BUNDLE_GEMFILE: ${{ inputs.saas && 'Gemfile.saas' || 'Gemfile' }} MYSQL_HOST: 127.0.0.1 MYSQL_PORT: 3306 MYSQL_USER: root From 1f8a4322ce1053ccd0df8ba3a2b256edfc3a78a5 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 13:05:30 +0100 Subject: [PATCH 45/49] Include engine tests in saas mode Notice that test is not a regular rake task you can enhance, but a hard-coded rails command. --- bin/rails | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/rails b/bin/rails index 9d935530de..8f0b2d8b3f 100755 --- a/bin/rails +++ b/bin/rails @@ -7,3 +7,5 @@ Fizzy.configure_bundle require_relative "../config/boot" require "rails/commands" + +Fizzy::Saas.append_test_paths if Fizzy.saas? && Rails.env.test? From aabb34e1fefd8e418cde0bfb2964740288c28be6 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 13:06:44 +0100 Subject: [PATCH 46/49] Update fizzy-saas to bring test support --- Gemfile.saas.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 21825b4b9a..b1be053417 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/basecamp/fizzy-saas - revision: 8b096fa8cb4c2823a726a0dae872214ac3642a34 + revision: 6f071760dddb5929d5ae1bab45ad917a1fd8c1fc specs: fizzy-saas (0.1.0) queenbee From 350bacb02a5a088b562431bcd79e715340643041 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 14:58:19 +0100 Subject: [PATCH 47/49] Split CI files to prevent PR workflows from accessing secrets --- .github/workflows/ci-main.yml | 12 ++++++++++++ .github/workflows/ci-pr.yml | 10 ++++++++++ .github/workflows/ci.yml | 29 ++++++++++++++--------------- 3 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/ci-main.yml create mode 100644 .github/workflows/ci-pr.yml diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml new file mode 100644 index 0000000000..7e79ee0e70 --- /dev/null +++ b/.github/workflows/ci-main.yml @@ -0,0 +1,12 @@ +name: CI (Main) + +on: + push: + +jobs: + call-ci: + uses: ./.github/workflows/ci.yml + with: + saas: true + secrets: + GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml new file mode 100644 index 0000000000..c6712ad938 --- /dev/null +++ b/.github/workflows/ci-pr.yml @@ -0,0 +1,10 @@ +name: CI (PR) + +on: + pull_request: + +jobs: + call-ci: + uses: ./.github/workflows/ci.yml + with: + saas: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b54f59b4a..5f207f3dbb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,14 @@ -name: CI +name: Reusable CI on: - push: - pull_request: + workflow_call: + inputs: + saas: + type: boolean + required: true + secrets: + GH_TOKEN: + required: false jobs: security: @@ -11,11 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - env: - BUNDLE_GITHUB__COM: "x-access-token:${{ secrets.GH_TOKEN }}" - BUNDLE_GEMFILE: Gemfile with: ruby-version: .ruby-version bundler-cache: true @@ -36,11 +38,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - env: - BUNDLE_GITHUB__COM: "x-access-token:${{ secrets.GH_TOKEN }}" - BUNDLE_GEMFILE: Gemfile with: ruby-version: .ruby-version bundler-cache: true @@ -60,8 +58,10 @@ jobs: db_adapter: sqlite - mode: MySQL db_adapter: mysql + # Only include SaaS if requested - mode: SaaS db_adapter: mysql + saas: ${{ inputs.saas }} services: mysql: @@ -85,8 +85,9 @@ jobs: MYSQL_HOST: 127.0.0.1 MYSQL_PORT: 3306 MYSQL_USER: root - FIZZY_DB_HOST: ${{ matrix.mode == 'SaaS' && '127.0.0.1' || '' }} - FIZZY_DB_PORT: ${{ matrix.mode == 'SaaS' && '3306' || '' }} + FIZZY_DB_HOST: 127.0.0.1 + FIZZY_DB_PORT: 3306 + BUNDLE_GITHUB__COM: ${{ inputs.saas && format('x-access-token:{0}', secrets.GH_TOKEN) || '' }} steps: - name: Install system packages @@ -95,8 +96,6 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - env: - BUNDLE_GITHUB__COM: "x-access-token:${{ secrets.GH_TOKEN }}" with: ruby-version: .ruby-version bundler-cache: true From 23980986ffc99bf66c951c2fc54ae4ce299ed889 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 14:58:43 +0100 Subject: [PATCH 48/49] Update fizzy-saas gem --- Gemfile.saas.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index b1be053417..bebc6098fa 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/basecamp/fizzy-saas - revision: 6f071760dddb5929d5ae1bab45ad917a1fd8c1fc + revision: 3bf089d1af610c828fae1e6a15d5d6b0fbce9b5b specs: fizzy-saas (0.1.0) queenbee From c8e2b4575fafa2ae3856422f86466d1e775f44cb Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Tue, 25 Nov 2025 15:00:56 +0100 Subject: [PATCH 49/49] Rename --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f207f3dbb..7984fca661 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Reusable CI +name: Common CI on: workflow_call: @@ -58,7 +58,6 @@ jobs: db_adapter: sqlite - mode: MySQL db_adapter: mysql - # Only include SaaS if requested - mode: SaaS db_adapter: mysql saas: ${{ inputs.saas }}