fly-atc 0.0.3-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a3e73181e40f1c2b1c8e8001465b63a6c212a652b8680e6a3e7a68cf5f162ec4
4
+ data.tar.gz: 3a378a5b22b364599268c5bf490f387a52bf211d5ba2c8dc6f355f5a665b65d5
5
+ SHA512:
6
+ metadata.gz: 4539778e9eee897c5ef2612d0cf35de36898237862ccca9072e051b3b9afb27d89da601285bb5a9cafaba07f5a909a4f1dbd1da1d406346981eac17917aee47d
7
+ data.tar.gz: b1cf03d2f33915db24cccca977ed85daa8b5399c63515bfc2790b69da1c58b0490354b0071687a1c951d0cdca8d3e0be0cb5ad91f5161a482f3cbed7b3821a3c
data/MIT-LICENSE ADDED
@@ -0,0 +1,85 @@
1
+ Copyright (c) Sam Ruby
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ ---
23
+
24
+ Includes code made available under the same license:
25
+
26
+ Copyright (c) 37signals, LLC
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining
29
+ a copy of this software and associated documentation files (the
30
+ "Software"), to deal in the Software without restriction, including
31
+ without limitation the rights to use, copy, modify, merge, publish,
32
+ distribute, sublicense, and/or sell copies of the Software, and to
33
+ permit persons to whom the Software is furnished to do so, subject to
34
+ the following conditions:
35
+
36
+ The above copyright notice and this permission notice shall be
37
+ included in all copies or substantial portions of the Software.
38
+
39
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
40
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
41
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
42
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
43
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
44
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
45
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
46
+ Copyright (c) 37signals, LLC
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of this software and associated documentation files (the
50
+ "Software"), to deal in the Software without restriction, including
51
+ without limitation the rights to use, copy, modify, merge, publish,
52
+ distribute, sublicense, and/or sell copies of the Software, and to
53
+ permit persons to whom the Software is furnished to do so, subject to
54
+ the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be
57
+ included in all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
60
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
61
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
62
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
63
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
64
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
65
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
66
+ Copyright (c) 37signals, LLC
67
+
68
+ Permission is hereby granted, free of charge, to any person obtaining
69
+ a copy of this software and associated documentation files (the
70
+ "Software"), to deal in the Software without restriction, including
71
+ without limitation the rights to use, copy, modify, merge, publish,
72
+ distribute, sublicense, and/or sell copies of the Software, and to
73
+ permit persons to whom the Software is furnished to do so, subject to
74
+ the following conditions:
75
+
76
+ The above copyright notice and this permission notice shall be
77
+ included in all copies or substantial portions of the Software.
78
+
79
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
80
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
81
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
82
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
83
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
84
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
85
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # fly-atc
2
+
3
+ A SaaS toolkit for converting a personal application into a efficient, siloed, multi-tenant application, where each user of your application is assigned a dedicated virtual machine.
4
+
5
+ ** Work in Progress **
6
+
7
+ ## Usage
8
+
9
+ ### Quickstart (single tenant):
10
+
11
+ ```
12
+ bundle add fly-atc
13
+ bundle binstubs fly-atc
14
+ ```
15
+
16
+ Replace `thruster` with `fly-atc` in Dockerfile.
17
+
18
+ ### Quickstart (multi-tenant):
19
+
20
+ ```
21
+ bundle add fly-atc
22
+ bin/rails generate atc
23
+ ```
24
+
25
+ Edit `config/atc.yml` as needed.
26
+
27
+ Fly.io's dockerfile generators will be able to help with this.
28
+
29
+ For approximately $1 US per month, you can run:
30
+ * [1 performance machine with 2Gb of RAM, 10GB of bandwidth, and 5GB of storage for 15 hours/month](https://fly.io/calculator?m=0_0_0_0_0&f=c&b=iad.10&a=no_none&r=shared_0_1_iad&t=10_100_5&u=0_1_100&g=1_performance_15_1_2048_iad_1024_0).
31
+ * [1 shared machine with 1Gb of RAM, 10GB of bandwidth, and 5GB of storage for 80 hours/month](https://fly.io/calculator?m=0_0_0_0_0&f=c&b=iad.10&a=no_none&r=shared_0_1_iad&t=10_100_5&u=0_1_100&g=1_shared_80_1_1048_iad_1024_0).
32
+
33
+ Vertical scaling can be achieved by adding more machines.
34
+
35
+ ## Motivation
36
+
37
+ I've been running my [Showcase](https://github.com/rubys/showcase?tab=readme-ov-file#showcase) software for nearly three years. Things have changed over time that I now want to take advantage of. I want take the opportunity to package those changes in the form of a toolkit that others can take advantage of.
38
+
39
+ From Wikipedia description of [SaaS](https://en.wikipedia.org/wiki/Software_as_a_service):
40
+
41
+ > SaaS customers have the abstraction of limitless computing resources, while [economy of scale](https://en.wikipedia.org/wiki/Economy_of_scale) drives down the cost. SaaS architectures are typically [multi-tenant](https://en.wikipedia.org/wiki/Multi-tenant); usually they share resources between clients for efficiency, but sometimes they offer a siloed environment for an additional fee.
42
+
43
+ The focus of this toolkit is efficient, siloed, multi-tenant applications *with no changes to the application*, taking advantage of:
44
+
45
+ * [Auto-suspend](https://community.fly.io/t/autosuspend-is-here-machine-suspension-is-enabled-everywhere/20942) - Virtual Machines that pop into existence when needed and disappear when not in use.
46
+ * [SQLite ready for production](https://rubyonrails.org/2024/11/7/rails-8-no-paas-required#getting-sqlite-ready-for-production) - raw performance coupled with operational compression of complexity; see [Supercharge the One Person Framework with SQLite: Rails World 2024](https://fractaledmind.github.io/2024/10/16/sqlite-supercharges-rails/).
47
+ * [Litestream](https://litestream.io/) - No-worry backups. Virtual machines can be literally destroyed and recreated elsewhere and start back up exactly where they left off.
48
+ * [Tigris Global Storage](https://fly.io/docs/tigris/) - globally caching, S3-compatible object storage.
49
+
50
+ That's a lot of moving parts. I've documented my [current architecture](https://github.com/rubys/showcase/blob/main/ARCHITECTURE.md) and published a [blueprint](https://fly.io/docs/blueprints/shared-nothing/).
51
+
52
+ The goal of fly-atc is to enable you configure multiple tenants and then not worry about this further, enabling you to focus on your application.
53
+
54
+ ## Approach
55
+
56
+ For illustrative purposes consider a SaaS Calender application implemented in Ruby on Rails using SQLite3 as the database. (My showcase application is a bit more involved than a calendar, but those details aren't important).
57
+
58
+ Key concepts:
59
+
60
+ * Each user/customer has a primarly location, and is assigned a single machine near that location. Such machines can be accessed from anywhere, but have lower latency near that location.
61
+ * Each user can have multiple calendars. Each calendar is associated with a single tenant on the user's machine. Each tenant consists a running instance of the web server application with one ([or more](https://rubyonrails.org/2024/11/7/rails-8-no-paas-required#a-solid-reduction-of-dependencies)) databases.
62
+
63
+ With that in mind, consider the following URL paths:
64
+
65
+ * `/bellevue/2025/winter/`
66
+ * `/bellevue/2025/summer-medal-ball/`
67
+ * `/bellevue/2025/summer-showcase/`
68
+ * `/boston/2025/april/`
69
+ * `/boston/2025/mini-comp/`
70
+ * `/boston/2025/october/`
71
+ * `/livermore/2025/the-music-of-prince/`
72
+ * `/livermore/2025/james-bond/`
73
+ * `/raleigh/2025/disney/`
74
+ * `/raleigh/2025/in-house/`
75
+
76
+ The first segment of the path identifies the user, and therefore the machine. The next two segments combined identify the tenant on that machine. This is but a subset of the planned showcases, you can see a [full list](https://smooth.fly.dev/showcase/) or even a [map](https://smooth.fly.dev/showcase/regions/) (click on the arrows under the map to move to different continents).
77
+
78
+ `fly-atc`'s responsibilities are to:
79
+ * Route requests to the correct machine
80
+ * Ensure databases are present/restored from backup
81
+ * Start/stop tenants as required
82
+ * Hand off requests to tenants
83
+
84
+ Rails 8 introduces [thruster](https://rubyonrails.org/2024/11/7/rails-8-no-paas-required#enter-kamal-2--thruster). `fly-atc` is a replacement for thruster:
85
+ * thruster requires no configuration, is limited to a single tenant.
86
+ * fly-atc enables multiple tenants, based on your configuration.
87
+
88
+ ## Implementation
89
+
90
+ Based on:
91
+ * [Thruster](https://github.com/basecamp/thruster) ([announcement](https://dev.37signals.com/thruster-released/))
92
+ * [tinyrp](https://github.com/pgaijin66/tinyrp) ([docs](https://prabeshthapa.medium.com/learn-reverse-proxy-by-creating-one-yourself-using-go-87be2a29d1e))
93
+
94
+ Near term plans:
95
+
96
+ * Remove certificate/https support
97
+ * Add launch on request / shutdown on idle
98
+ * Add [fly-replay](https://fly.io/docs/networking/dynamic-request-routing/)
99
+
100
+ On the radar:
101
+
102
+ * Support for targets other than fly.io.
103
+ * Support for platforms other than Rails, likely starting with Node, and focusing on popular ORMs: [Prisma](https://www.prisma.io/), [TypeORM](https://typeorm.io/), and [Sequelize](https://sequelize.org/).
104
+ * Dashboard. One should be able to deploy new users and make other configuration changes using only your cell phone. I [do this today](https://github.com/rubys/showcase/blob/main/ARCHITECTURE.md#administration) with my showcase application.
data/exe/fly-atc ADDED
@@ -0,0 +1,11 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ PLATFORM = [ :cpu, :os ].map { |m| Gem::Platform.local.send(m) }.join("-")
4
+ EXECUTABLE = File.expand_path(File.join(__dir__, PLATFORM, "fly-atc"))
5
+
6
+ if File.exist?(EXECUTABLE)
7
+ exec(EXECUTABLE, *ARGV)
8
+ else
9
+ STDERR.puts("ERROR: Unsupported platform: #{PLATFORM}")
10
+ exit 1
11
+ end
Binary file
@@ -0,0 +1,3 @@
1
+ module FlyAtc
2
+ VERSION = "0.0.3"
3
+ end
data/lib/fly-atc.rb ADDED
@@ -0,0 +1,11 @@
1
+ module FlyAtc
2
+ end
3
+
4
+ require_relative "fly-atc/version"
5
+ require_relative "helpers/atc-cable"
6
+
7
+ class FlyAtcRailtie < Rails::Railtie
8
+ rake_tasks do
9
+ Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f }
10
+ end
11
+ end
@@ -0,0 +1,44 @@
1
+ class AtcGenerator < Rails::Generators::Base
2
+ def generate_app
3
+ source_paths.push File.expand_path("./templates", __dir__)
4
+
5
+ ### config/routes.rb
6
+
7
+ @routes = IO.read("config/routes.rb")
8
+
9
+ unless @routes.include? "fly_atc_scope"
10
+ _, prolog, routes = routes.split(/(.*Rails.application.routes.draw do\n)/m,2)
11
+ routes, epilog, _ = @routes.split(/^(end.*)/m,2)
12
+ routes = routes.split(/\n\s*\n/)
13
+ scoped = routes.select {|route| route =~ /^\s*\w/ && !route.include?('as:')}
14
+
15
+ @routes = <<~EOF
16
+ #{prolog.rstrip}
17
+ fly_atc_scope = ENV.fetch("FLY_ATC_SCOPE", "")
18
+
19
+ unless fly_atc_scope == ""
20
+ mount ActionCable.server => "/\#{fly_atc_scope}/cable"
21
+ end
22
+
23
+ scope fly_atc_scope do
24
+ #{scoped.join("\n\n").gsub(/^ /, " ")}
25
+ end
26
+
27
+ #{(routes-scoped).join("\n\n").rstrip}
28
+ #{epilog.rstrip}
29
+ EOF
30
+ end
31
+
32
+ template "routes.erb", "config/routes.rb"
33
+
34
+ ### app/views/layouts/application.html.erb
35
+
36
+ @layout = IO.read("app/views/layouts/application.html.erb")
37
+
38
+ unless @layout.include? "action_cable_meta_tag_dynamic"
39
+ @layout[/<meta.*?\n()\r?\n/m, 1] = " <%= action_cable_meta_tag_dynamic %>\n"
40
+ end
41
+
42
+ template "application.html.erb", "app/views/layouts/application.html.erb"
43
+ end
44
+ end
@@ -0,0 +1 @@
1
+ <%= @layout -%>
@@ -0,0 +1 @@
1
+ <%= @routes -%>
@@ -0,0 +1,17 @@
1
+ module ApplicationHelper
2
+ def action_cable_meta_tag_dynamic
3
+ scheme = (request.env['HTTP_X_FORWARDED_PROTO'] || request.env["rack.url_scheme"] || '').split(',').last
4
+ return '' if scheme.blank?
5
+ host = request.env['HTTP_X_FORWARDED_HOST'] || request.env["HTTP_HOST"]
6
+ scope = ENV.fetch('FLY_ATC_SCOPE', "")
7
+ root = request.env['RAILS_RELATIVE_URL_ROOT']
8
+
9
+ if scope != ""
10
+ websocket = "#{scheme.sub('http', 'ws')}://#{host}#{root}#{scope}/cable"
11
+ else
12
+ websocket = "#{scheme.sub('http', 'ws')}://#{host}#{root}/cable"
13
+ end
14
+
15
+ "<meta name=\"action-cable-url\" content=\"#{websocket}\" />".html_safe
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ actions = Rake::Task["db:prepare"].actions.clone
2
+
3
+ namespace :db do
4
+ task :atc_prepare => "db:load_config" do
5
+ actions.each {|action| action.call}
6
+ end
7
+ end
8
+
9
+ Rake::Task["db:prepare"].clear
10
+
11
+ namespace :litestream do
12
+ task :atc_config => "db:load_config" do
13
+ require 'erubi'
14
+
15
+ @dbs =
16
+ ActiveRecord::Base
17
+ .configurations
18
+ .configs_for(env_name: "production", include_hidden: true)
19
+ .select { |config| ["sqlite3", "litedb"].include? config.adapter }
20
+ .map(&:database)
21
+
22
+ @config = ENV["LITESTREAM_CONFIG"] || Rails.root.join("config/litestream.yml")
23
+
24
+ template = File.read(File.join(File.dirname(__FILE__), "templates/litestream.yml.erb"))
25
+ result = eval(Erubi::Engine.new(template).src)
26
+
27
+ unless File.exist?(@config) && File.read(@config) == result
28
+ File.write(@config, result)
29
+ end
30
+ end
31
+
32
+ task :atc_restore => "litestream:atc_config" do
33
+ next unless ENV["BUCKET_NAME"]
34
+
35
+ @dbs.each do |database|
36
+ next if File.exist? database
37
+ sh "bundle exec litestream restore -config #{@config} -if-replica-exists #{database}"
38
+ end
39
+ end
40
+
41
+ task :atc_replicate => "litestream:atc_config" do
42
+ next unless ENV["BUCKET_NAME"]
43
+ sh "bundle exec litestream replicate -config #{@config}"
44
+ end
45
+ end
46
+
47
+ namespace :atc do
48
+ task :prepare => ["litestream:atc_restore", "db:atc_prepare"]
49
+ end
@@ -0,0 +1,14 @@
1
+ # This is the configuration file for litestream.
2
+ #
3
+ # For more details, see: https://litestream.io/reference/config/
4
+ #
5
+ dbs:
6
+ <% for db in @dbs -%>
7
+ - path: <%= db %>
8
+ replicas:
9
+ - type: s3
10
+ bucket: $BUCKET_NAME
11
+ path: storage/<%= File.basename(db) %>
12
+ access-key-id: $AWS_ACCESS_KEY_ID
13
+ secret-access-key: $AWS_SECRET_ACCESS_KEY
14
+ <% end -%>
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fly-atc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: x86_64-linux
6
+ authors:
7
+ - Sam Ruby
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-11-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: litestream
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.10'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0.10'
41
+ description: An HTTP/2 proxy for mutli-tenant production deployments
42
+ email: rubys@intertwingly.net
43
+ executables:
44
+ - fly-atc
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - exe/fly-atc
51
+ - exe/x86_64-linux/fly-atc
52
+ - lib/fly-atc.rb
53
+ - lib/fly-atc/version.rb
54
+ - lib/generators/atc_generator.rb
55
+ - lib/generators/templates/application.html.erb
56
+ - lib/generators/templates/routes.erb
57
+ - lib/helpers/atc-cable.rb
58
+ - lib/tasks/atc.rake
59
+ - lib/tasks/templates/litestream.yml.erb
60
+ homepage: https://github.com/rubys/fly-atc
61
+ licenses:
62
+ - MIT
63
+ metadata:
64
+ homepage_uri: https://github.com/rubys/fly-atc
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.5.18
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: A SaaS toolkit
84
+ test_files: []