fly.io-rails 0.1.7 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd72efc17bf3e1b7826d569f0e72dcb500fb1b49a116b2c2f84805b3d9c05ecc
4
- data.tar.gz: 7154bc14b25e0267d34c461359577bd8a25d9ed5c70cf329b0533d32100e2116
3
+ metadata.gz: 5607b08f46147018d9b5915b484ccb3d20f76a662aee1b381d5576cfc9e6b5f9
4
+ data.tar.gz: 0e7ae042c84afd01f3fa6ab90dab72a4720b7f5e816d9859c59007ebad06635b
5
5
  SHA512:
6
- metadata.gz: 345f8065e2fcf7515b27022965ee4669d6d03eb38c713114a77da92806b4063c2860fd35eb3208ad55eb53852797043a05f3dc0fd711c5ac8263e603ada1c8ab
7
- data.tar.gz: 4ce36e49a13734dbedc5c5e4663d9fdb616f44aab70bdf7c63e0ca342b536bdaac820680636fd3bb80ea0c94d648f0fe52b5c58f8669ee938c7d482552b952cb
6
+ metadata.gz: 1849e0cca1382bc6df929ab82a777bbe9586be9bf6ad6b26e2fe5f8942fc43ef8661c8ea46cddafb53eb6997f7b1ff4611b2f4ecbd896a2fe5160aaf77ad6077
7
+ data.tar.gz: 13ceffa34870da0b65bbb16d488d730e12e9592f6862cb50d594f9bf034dda62f8251ea6878634b8a682b1c060dbbefb63f3484bd9411c3cba4fe462e0b54f2a
data/README.md CHANGED
@@ -10,6 +10,25 @@ For usage instructions, see the following guides:
10
10
  * [Lite FS](https://fly.io/docs/rails/advanced-guides/litefs/)
11
11
  * [Terraform](https://fly.io/docs/rails/advanced-guides/terraform/)
12
12
 
13
+ ## Generator options
14
+
15
+ * `--name` name of the application. If a name is not provided, one will be generated for you.
16
+ * `--org` the organization to operate on. Defaults to `personal`.
17
+ * `--region` region to launch the application in. Accepts multiple values, and can be specified multiple times.
18
+ * `--nomad` generates a nomad application instead of a machines application.
19
+ * `--litefs` adds support for replicated sqlite3 databases via [litefs](https://fly.io/blog/introducing-litefs/). Only works on nomad machines currently.
20
+ * `--passenger` run your Rails application with [nginx](https://www.nginx.com/) and [Phusion Passenger](https://www.phusionpassenger.com/).
21
+ * `--serverless` configures your application to exit after 5 minutes of inactivity. Machines will automatically restart when next accessed. Only works with passenger currently.
22
+
23
+ ## Automatically detected features
24
+
25
+ * _ruby_: the deployed application will use the same version of ruby and bundler as your development environment.
26
+ * _node_: if the use of node is detected, node, yarn, and your npm packages will be installed.
27
+ * _sqlite3_: if the production database is sqlite3 a volume will be allocated and the database will be put there.
28
+ * _postgres_: if the production database is postgres a postgres machine will be allocated
29
+ * _redis_: if redis is used for action cable, caching, or sidekiq your redis database will be added to this application. If you don't currently have a redis database, one will be allocated. If redis is used for caching, eviction will be turned on.
30
+ * _sidekiq_: if sidekiq is used it will be launched along side of your rails application.
31
+
13
32
  ## Key files
14
33
 
15
34
  * Entrypoints: [lib/tasks/fly.rake](./lib/tasks/fly.rake), [lib/generators/app_generator.rb](./lib/generators/app_generator.rb), [lib/generators/terraform_generator.rb](.lib/generators/terraform_generator.rb) contain the deploy task, fly:app generator and
@@ -24,6 +24,7 @@ module Fly
24
24
  # extract options
25
25
  self.app = app
26
26
  regions = options[:region]&.flatten || []
27
+ @avahi = options[:avahi]
27
28
  @litefs = options[:litefs]
28
29
  @nomad = options[:nomad]
29
30
  @passenger = options[:passenger]
@@ -58,11 +59,43 @@ module Fly
58
59
 
59
60
  # set additional variables based on application source
60
61
  scan_rails_app
62
+ @redis = :internal if options[:redis]
63
+ if File.exist? 'Procfile.fly'
64
+ @redis = :internal if IO.read('Procfile.fly') =~ /^redis/
65
+ end
66
+
67
+ if options[:anycable] and not @anycable
68
+ # read and remove original config
69
+ original_config = YAML.load_file 'config/cable.yml'
70
+ File.unlink 'config/cable.yml'
71
+
72
+ # add and configure anycable-rails
73
+ say_status :run, 'bundle add anycable-rails'
74
+ Bundler.with_original_env do
75
+ system 'bundle add anycable-rails'
76
+ system 'bin/rails generate anycable:setup --skip-heroku --skip-procfile-dev --skip-jwt --devenv=skip'
77
+ end
78
+
79
+ # insert action_cable_meta_tag
80
+ insert_into_file 'app/views/layouts/application.html.erb',
81
+ " <%= action_cable_meta_tag %>\n",
82
+ after: "<%= csp_meta_tag %>\n"
83
+
84
+ # copy production environment to original config
85
+ anycable_config = YAML.load_file 'config/cable.yml'
86
+ original_config['production'] = anycable_config['production']
87
+ File.write 'config/cable.yml', YAML.dump(original_config)
88
+
89
+ @anycable = true
90
+ end
61
91
 
62
92
  # determine processes
63
93
  @procs = {web: 'bin/rails server'}
64
94
  @procs[:web] = "nginx -g 'daemon off;'" if @passenger
65
95
  @procs[:worker] = 'bundle exec sidekiq' if @sidekiq
96
+ @procs[:redis] = 'redis-server /etc/redis/redis.conf' if @redis
97
+ @procs.merge! 'anycable-rpc': 'bundle exec anycable',
98
+ 'anycable-go': '/usr/local/bin/anycable-go --port=8082' if @anycable
66
99
  end
67
100
 
68
101
  def app
@@ -143,7 +176,9 @@ module Fly
143
176
  end
144
177
 
145
178
  def generate_patches
146
- if @redis_cable and not File.exist? 'config/initializers/action_cable.rb'
179
+ if @redis_cable and not @anycable and @redis != :internal and
180
+ not File.exist? 'config/initializers/action_cable.rb'
181
+
147
182
  app
148
183
  template 'patches/action_cable.rb', 'config/initializers/action_cable.rb'
149
184
  end
@@ -204,7 +239,7 @@ module Fly
204
239
  start = Fly::Machines.create_and_start_machine(app, config: config)
205
240
  machine = start[:id]
206
241
 
207
- if !machine
242
+ if not machine
208
243
  STDERR.puts 'Error starting release machine'
209
244
  PP.pp start, STDERR
210
245
  exit 1
@@ -214,24 +249,27 @@ module Fly
214
249
  timeout: 60, state: 'started'
215
250
 
216
251
  # wait for release to copmlete
217
- status = nil
218
252
  5.times do
219
253
  status = Fly::Machines.wait_for_machine app, machine,
220
- timeout: 60, state: 'stopped'
221
- return machine if status[:ok]
254
+ instance_id: start[:instance_id], timeout: 60, state: 'stopped'
255
+ break if status[:ok]
222
256
  end
223
257
 
224
- # wait for release to copmlete
225
- event = nil
226
- 90.times do
227
- sleep 1
228
- status = Fly::Machines.get_a_machine app, machine
229
- event = status[:events]&.first
230
- return machine if event && event[:type] == 'exit'
231
- end
258
+ if status and status[:ok]
259
+ event = nil
260
+ 300.times do
261
+ status = Fly::Machines.get_a_machine app, start[:id]
262
+ event = status[:events]&.first
263
+ break if event[:type] == 'exit'
264
+ sleep 0.2
265
+ end
232
266
 
233
- STDERR.puts event.to_json
234
- exit 1
267
+ exit_code = event&.dig(:request, :exit_event, :exit_code)
268
+ Fly::Machines.delete_machine app, machine if machine
269
+ return event, exit_code, machine
270
+ else
271
+ return status, nil, nil
272
+ end
235
273
  end
236
274
 
237
275
  def launch(app)
@@ -263,7 +301,7 @@ module Fly
263
301
  end
264
302
  end
265
303
 
266
- if @redis and not secrets.include? 'REDIS_URL'
304
+ if @redis and @redis != :internal and not secrets.include? 'REDIS_URL'
267
305
  # Set eviction policy to true if a cache provider, else false.
268
306
  eviction = @redis_cache ? '--enable-eviction' : '--disable-eviction'
269
307
 
@@ -313,22 +351,30 @@ module Fly
313
351
 
314
352
  # perform release
315
353
  say_status :fly, release_config[:env]['SERVER_COMMAND']
316
- machine = release(app, release_config)
317
- Fly::Machines.delete_machine app, machine if machine
318
-
319
- # start proxy, if necessary
320
- endpoint = Fly::Machines::fly_api_hostname!
354
+ event, exit_code, machine = release(app, release_config)
321
355
 
322
- # stop previous instances - list will fail on first run
323
- stdout, stderr, status = Open3.capture3('fly machines list --json')
324
- unless stdout.empty?
325
- JSON.parse(stdout).each do |list|
326
- next if list['id'] == machine
327
- system "fly machines remove --force #{list['id']}"
328
- end
356
+ if exit_code != 0
357
+ STDERR.puts 'Error performing release'
358
+ STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
359
+ STDERR.puts "run 'flyctl logs --instance #{machine}' for more information"
360
+ exit 1
329
361
  end
330
362
  end
331
363
 
364
+ # start proxy, if necessary
365
+ endpoint = Fly::Machines::fly_api_hostname!
366
+
367
+ # stop previous instances - list will fail on first run
368
+ stdout, stderr, status = Open3.capture3('fly machines list --json')
369
+ unless stdout.empty?
370
+ JSON.parse(stdout).each do |list|
371
+ next if list['id'] == machine or list['state'] == 'destroyed'
372
+ cmd = "fly machines remove --force #{list['id']}"
373
+ say_status :run, cmd
374
+ system cmd
375
+ end
376
+ end
377
+
332
378
  # configure sqlite3 (can be overridden by fly.toml)
333
379
  if @sqlite3
334
380
  config[:mounts] = [
@@ -354,38 +400,52 @@ module Fly
354
400
  end
355
401
 
356
402
  # start app
403
+ machines = {}
357
404
  say_status :fly, "start #{app}"
358
- start = Fly::Machines.create_and_start_machine(app, config: config)
359
- machine = start[:id]
405
+ if not toml['processes']
406
+ start = Fly::Machines.create_and_start_machine(app, config: config)
407
+ machines['app'] = start[:id]
408
+ else
409
+ config[:env] ||= {}
410
+ toml['processes'].each do |name, entrypoint|
411
+ config[:env]['SERVER_COMMAND'] = entrypoint
412
+ start = Fly::Machines.create_and_start_machine(app, config: config)
413
+ machines[name] = start[:id]
414
+ end
415
+ end
360
416
 
361
- if !machine
417
+ if machines.empty?
362
418
  STDERR.puts 'Error starting application'
363
419
  PP.pp start, STDERR
364
420
  exit 1
365
421
  end
366
422
 
367
- 5.times do
368
- status = Fly::Machines.wait_for_machine app, machine,
369
- timeout: 60, status: 'started'
370
- return if status[:ok]
423
+ timeout = Time.now + 300
424
+ while Time.now < timeout and not machines.empty?
425
+ machines.each do |name, machine|
426
+ status = Fly::Machines.wait_for_machine app, machine,
427
+ timeout: 10, status: 'started'
428
+ machines.delete name if status[:ok]
429
+ end
371
430
  end
372
431
 
373
- STDERR.puts 'Timeout waiting for application to start'
432
+ unless machines.empty?
433
+ STDERR.puts 'Timeout waiting for application to start'
434
+ end
374
435
  end
375
436
 
376
437
  def terraform(app, image)
377
- # update main.tf with the image name
378
- tf = IO.read('main.tf')
379
- tf[/^\s*image\s*=\s*"(.*?)"/, 1] = image.strip
380
- IO.write 'main.tf', tf
381
-
382
- # find first machine in terraform config file
383
- machines = Fly::HCL.parse(IO.read('main.tf')).find {|block|
384
- block.keys.first == :resource and
385
- block.values.first.keys.first == 'fly_machine'}
438
+ # find first machine using the image ref in terraform config file
439
+ machine = Fly::HCL.parse(IO.read('main.tf')).
440
+ map {|block| block.dig(:resource, 'fly_machine')}.compact.
441
+ find {|machine| machine.values.first[:image] == 'var.image_ref'}
442
+ if not machine
443
+ STDERR.puts 'unable to find fly_machine with image = var.image_ref in main.rf'
444
+ exit 1
445
+ end
386
446
 
387
447
  # extract HCL configuration for the machine
388
- config = machines.values.first.values.first.values.first
448
+ config = machine.values.first
389
449
 
390
450
  # delete HCL specific configuration items
391
451
  %i(services for_each region app name depends_on).each do |key|
@@ -407,40 +467,25 @@ module Fly
407
467
  config[:env] ||= {}
408
468
  config[:env]['SERVER_COMMAND'] = 'bin/rails fly:release'
409
469
 
470
+ # fill in image
471
+ config[:image] = image
472
+
410
473
  # start proxy, if necessary
411
474
  endpoint = Fly::Machines::fly_api_hostname!
412
475
 
413
- # start release machine
414
- STDERR.puts "--> #{config[:env]['SERVER_COMMAND']}"
415
- start = Fly::Machines.create_and_start_machine(app, config: config)
416
- machine = start[:id]
417
-
418
- if !machine
419
- STDERR.puts 'Error starting release machine'
420
- PP.pp start, STDERR
421
- exit 1
422
- end
423
-
424
- # wait for release to copmlete
425
- event = nil
426
- 90.times do
427
- sleep 1
428
- status = Fly::Machines.get_a_machine app, machine
429
- event = status[:events]&.first
430
- break if event && event[:type] == 'exit'
476
+ # perform release, if necessary
477
+ if (IO.read('lib/tasks/fly.rake') rescue '') =~ /^\s*task[ \t]*+:?release"?[ \t]*\S/
478
+ say_status :fly, config[:env]['SERVER_COMMAND']
479
+ event, exit_code, machine = release(app, config)
480
+ else
481
+ exit_code = 0
431
482
  end
432
483
 
433
- # extract exit code
434
- exit_code = event.dig(:request, :exit_event, :exit_code)
435
-
436
484
  if exit_code == 0
437
- # delete release machine
438
- Fly::Machines.delete_machine app, machine
439
-
440
485
  # use terraform apply to deploy
441
486
  ENV['FLY_API_TOKEN'] = `flyctl auth token`.chomp
442
487
  ENV['FLY_HTTP_ENDPOINT'] = endpoint if endpoint
443
- system 'terraform apply -auto-approve'
488
+ system "terraform apply -auto-approve -var=\"image_ref=#{image}\""
444
489
  else
445
490
  STDERR.puts 'Error performing release'
446
491
  STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
@@ -12,7 +12,9 @@ module Fly
12
12
  @postgresql = true
13
13
  end
14
14
 
15
- @sidekiq = IO.read('Gemfile').include? 'sidekiq' rescue false
15
+ gemfile = IO.read('Gemfile') rescue ''
16
+ @sidekiq = gemfile.include? 'sidekiq'
17
+ @anycable = gemfile.include? 'anycable'
16
18
 
17
19
  @cable = ! Dir['app/channels/*.rb'].empty?
18
20
 
@@ -29,6 +29,8 @@ module FlyIoRails
29
29
  end
30
30
 
31
31
  def create_app(name: nil, org: 'personal', regions: [], nomad: false, **rest)
32
+ return if File.exist? 'fly.toml'
33
+
32
34
  cmd = if name
33
35
  "flyctl apps create #{name.inspect} --org #{org.inspect} --machines"
34
36
  else
@@ -1,3 +1,3 @@
1
1
  module Fly_io
2
- VERSION = '0.1.7'
2
+ VERSION = '0.1.9'
3
3
  end
@@ -9,7 +9,10 @@ class AppGenerator < Rails::Generators::Base
9
9
  class_option :region, type: :array, repeatable: true, default: []
10
10
  class_option :nomad, type: :boolean, default: false
11
11
 
12
+ class_option :anycable, type: :boolean, default: false
13
+ class_option :avahi, type: :boolean, default: false
12
14
  class_option :litefs, type: :boolean, default: false
15
+ class_option :redis, type: :boolean, default: false
13
16
  class_option :passenger, type: :boolean, default: false
14
17
  class_option :serverless, type: :boolean, default: false
15
18
 
@@ -9,6 +9,8 @@ class TerraformGenerator < Rails::Generators::Base
9
9
  class_option :region, type: :array, repeatable: true, default: []
10
10
 
11
11
  class_option :litefs, type: :boolean, default: false
12
+ class_option :passenger, type: :boolean, default: false
13
+ class_option :serverless, type: :boolean, default: false
12
14
 
13
15
  def terraform
14
16
  source_paths.push File.expand_path('../templates', __dir__)
@@ -20,6 +22,7 @@ class TerraformGenerator < Rails::Generators::Base
20
22
  action.generate_toml
21
23
  action.generate_dockerfile
22
24
  action.generate_dockerignore
25
+ action.generate_nginx_conf
23
26
  action.generate_terraform
24
27
  action.generate_raketask
25
28
  action.generate_procfile
@@ -30,9 +30,12 @@ ARG BUNDLER_VERSION=<%= @bundler_version %>
30
30
 
31
31
  ARG RAILS_ENV=production
32
32
  ENV RAILS_ENV=${RAILS_ENV}
33
-
34
- ENV RAILS_SERVE_STATIC_FILES true
33
+ <% if @anycable or not @passenger -%>
35
34
  ENV RAILS_LOG_TO_STDOUT true
35
+ <% end -%>
36
+ <% unless @passenger -%>
37
+ ENV RAILS_SERVE_STATIC_FILES true
38
+ <% end -%>
36
39
 
37
40
  ARG BUNDLE_WITHOUT=development:test
38
41
  ARG BUNDLE_PATH=vendor/bundle
@@ -81,9 +84,6 @@ RUN gem update --system --no-document && \
81
84
 
82
85
  COPY Gemfile* ./
83
86
  RUN bundle install && rm -rf vendor/bundle/ruby/*/cache
84
- <% if @procs.length > 1 -%>
85
- RUN gem install foreman
86
- <% end -%>
87
87
 
88
88
  <% if @node -%>
89
89
  #######################################################################
@@ -113,23 +113,27 @@ FROM flyio/litefs:pr-109 AS litefs
113
113
 
114
114
  FROM base
115
115
 
116
+ <% if @passenger -%>
117
+ # add passenger repository
118
+ RUN apt-get install -y dirmngr gnupg apt-transport-https ca-certificates curl && \
119
+ curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key.txt | \
120
+ gpg --dearmor > /etc/apt/trusted.gpg.d/phusion.gpg && \
121
+ sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bullseye main > /etc/apt/sources.list.d/passenger.list'
122
+
123
+ <% end -%>
116
124
  <%
117
125
  @deploy_packages = %w(file vim curl gzip)
118
126
  @deploy_packages += %w(nginx passenger libnginx-mod-http-passenger) if @passenger
119
127
  @deploy_packages << 'postgresql-client' if @postgresql
120
128
  @deploy_packages << 'libsqlite3-0' if @sqlite3
121
129
  @deploy_packages << 'fuse' if @litefs
130
+ @deploy_packages << 'ruby-foreman' if @procs.length > 1
131
+ @deploy_packages << 'redis-server' if @redis == :internal
132
+ @deploy_packages += %w(avahi-daemon avahi-utils libnss-mdns) if @avahi
122
133
  -%>
123
134
  ARG DEPLOY_PACKAGES=<%= @deploy_packages.join(' ').inspect %>
124
135
  ENV DEPLOY_PACKAGES=${DEPLOY_PACKAGES}
125
136
 
126
- <% if @passenger -%>
127
- RUN apt-get install -y dirmngr gnupg apt-transport-https ca-certificates curl && \
128
- curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key.txt | \
129
- gpg --dearmor > /etc/apt/trusted.gpg.d/phusion.gpg && \
130
- sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bullseye main > /etc/apt/sources.list.d/passenger.list'
131
-
132
- <% end -%>
133
137
  RUN --mount=type=cache,id=prod-apt-cache,sharing=locked,target=/var/cache/apt \
134
138
  --mount=type=cache,id=prod-apt-lib,sharing=locked,target=/var/lib/apt \
135
139
  apt-get update -qq && \
@@ -137,6 +141,17 @@ RUN --mount=type=cache,id=prod-apt-cache,sharing=locked,target=/var/cache/apt \
137
141
  ${DEPLOY_PACKAGES} \
138
142
  && rm -rf /var/lib/apt/lists /var/cache/apt/archives
139
143
 
144
+ <% if @anycable -%>
145
+ # install anycable
146
+ RUN curl -L https://github.com/anycable/anycable-go/releases/download/v1.2.1/anycable-go-linux-amd64 -o /usr/local/bin/anycable-go && chmod 755 /usr/local/bin/anycable-go
147
+
148
+ <% end -%>
149
+ <% if @redis == :internal -%>
150
+ # configure redis
151
+ RUN sed -i 's/^daemonize yes/daemonize no/' /etc/redis/redis.conf &&\
152
+ sed -i 's/^logfile/# logfile/' /etc/redis/redis.conf
153
+
154
+ <% end -%>
140
155
  # copy installed gems
141
156
  COPY --from=gems /app /app
142
157
  COPY --from=gems /usr/lib/fullstaq-ruby/versions /usr/lib/fullstaq-ruby/versions
@@ -158,15 +173,32 @@ ADD config/litefs.yml /etc/litefs.yml
158
173
  RUN mkdir /data
159
174
  <% end -%>
160
175
  #######################################################################
176
+ <% if @avahi -%>
177
+
178
+ # configure avahi for ipv6
179
+ RUN sed -i 's/mdns4_minimal/mdns_minimal/' /etc/nsswitch.conf
180
+ <% end -%>
161
181
  <% if @passenger -%>
162
182
 
163
183
  # configure nginx/passenger
164
184
  COPY config/nginx.conf /etc/nginx/sites-available/rails.conf
165
185
  RUN rm /etc/nginx/sites-enabled/default && \
166
- ln -s /etc/nginx/sites-available/rails.conf /etc/nginx/sites-enabled/
186
+ ln -s /etc/nginx/sites-available/rails.conf /etc/nginx/sites-enabled/ && \
187
+ sed -i 's/user .*;/user root;/' /etc/nginx/nginx.conf && \
188
+ sed -i '/^include/i include /etc/nginx/main.d/*.conf;' /etc/nginx/nginx.conf && \
189
+ mkdir /etc/nginx/main.d && \
190
+ echo 'env RAILS_MASTER_KEY;' >> /etc/nginx/main.d/env.conf &&\
191
+ <% if @redis -%>
192
+ echo 'env REDIS_URL;' >> /etc/nginx/main.d/env.conf &&\
193
+ <% end -%>
194
+ <% if @anycable -%>
195
+ echo 'env ANYCABLE_RPC_HOST;' >> /etc/nginx/main.d/env.conf &&\
196
+ echo 'env CABLE_URL;' >> /etc/nginx/main.d/env.conf &&\
197
+ <% end -%>
198
+ echo 'env RAILS_LOG_TO_STDOUT;' >> /etc/nginx/main.d/env.conf
167
199
  <% if @serverless -%>
168
200
  COPY config/hook_detached_process /etc/nginx/
169
- <% end -%>
201
+ <% end -%>
170
202
  <% end -%>
171
203
 
172
204
  # Deploy your application
@@ -17,20 +17,46 @@ namespace :fly do
17
17
  task :release => 'db:migrate'
18
18
  <%- end -%>
19
19
 
20
+ <% end -%>
21
+ <% if @avahi -%>
22
+ task :env do
23
+ <% if @redis -%>
24
+ ENV['REDIS_URL'] = "redis://#{ENV['FLY_REGION']}-redis.local:6379/1"
25
+ <% end -%>
26
+ <% if @anycable -%>
27
+ ENV['ANYCABLE_RPC_HOST'] = "#{ENV['FLY_REGION']}-anycable-rpc.local:50051"
28
+ ENV['CABLE_URL'] = "#{ENV['FLY_REGION']}-anycable-go.local"
29
+ <% end -%>
30
+ end
31
+
20
32
  <% end -%>
21
33
  # SERVER step:
22
34
  # - changes to the filesystem made here are deployed
23
35
  # - full access to secrets, databases
24
36
  # - failures here result in VM being stated, shutdown, and rolled back
25
37
  # to last successful deploy (if any).
26
- <%- if @sqlite3 -%>
27
- task :server => [:swapfile, 'db:migrate'] do
28
- <%- else -%>
29
- task :server => :swapfile do
30
- <%- end -%>
38
+ <%= begin
39
+ deps = [:swapfile]
40
+ deps << 'db:migrate' if @sqlite3
41
+ deps = deps.first if deps.length == 1
42
+
43
+ if @procs.length > 1 ? ':server, [:formation]' : ':server'
44
+ "task :server, [:formation] => #{deps.inspect} do |task, args|"
45
+ else
46
+ "task :server => #{deps.inspect} do"
47
+ end
48
+ end %>
31
49
  <%- if @procs.length > 1 -%>
50
+ formation = args[:formation] || <%= @procs.keys.map {|key| "#{key}=1"}.join(';').inspect %>
51
+ formation.gsub! ';', ','
52
+ <%- if @avahi -%>
53
+ Rake::Task['fly:avahi_publish'].invoke(formation)
54
+ <%- end -%>
32
55
  Bundler.with_original_env do
33
- sh 'foreman start --procfile=Procfile.fly'
56
+ <%- if @avahi -%>
57
+ # Rake::Task['fly:env'].invoke
58
+ <%- end -%>
59
+ sh "foreman start --procfile=Procfile.fly --formation=#{formation}"
34
60
  end
35
61
  <%- else -%>
36
62
  sh <%= @procs.values.first.inspect %>
@@ -53,5 +79,8 @@ namespace :fly do
53
79
  sh 'mkswap /swapfile'
54
80
  sh 'echo 10 > /proc/sys/vm/swappiness'
55
81
  sh 'swapon /swapfile'
82
+ <% if @redis == :internal -%>
83
+ sh 'echo 1 > /proc/sys/vm/overcommit_memory'
84
+ <% end -%>
56
85
  end
57
86
  end
@@ -1,7 +1,9 @@
1
1
  app = "<%= @app %>"
2
2
  kill_signal = "SIGINT"
3
3
  kill_timeout = 5
4
+ <% unless @avahi -%>
4
5
  processes = []
6
+ <% end -%>
5
7
 
6
8
  [build]
7
9
  [build.args]
@@ -23,6 +25,11 @@ processes = []
23
25
  <% else -%>
24
26
  DATABASE_URL = "sqlite3:///mnt/volume/production.sqlite3"
25
27
  <% end -%>
28
+ <% if @avahi -%>
29
+
30
+ [processes]
31
+ app = "bin/rails fly:server[<%= @procs.keys.map {|key| "#{key}=1"}.join(';') %>]"
32
+ <% end -%>
26
33
 
27
34
  [mounts]
28
35
  source = <%= "#{app.gsub('-', '_')}_volume".inspect %>
@@ -3,9 +3,5 @@
3
3
  status = `passenger-status`
4
4
 
5
5
  processes = status[/^Processes\s*:\s*(\d*)/, 1].to_i
6
- <% if @cable -%>
7
- cable = status[/^<%= @app %>-cable.*?\n\n/m]
8
- processes -= 1 if cable and cable =~ /Sessions:\s*[1-9]/
9
- <% end -%>
10
6
 
11
- system 'nginx -s stop' if processes == 0
7
+ system 'nginx -s stop' if processes == 0
@@ -2,11 +2,16 @@ terraform {
2
2
  required_providers {
3
3
  fly = {
4
4
  source = "fly-apps/fly"
5
- version = "0.0.18"
5
+ version = "0.0.20"
6
6
  }
7
7
  }
8
8
  }
9
9
 
10
+ variable "image_ref" {
11
+ type = string
12
+ description = "docker images containing the application"
13
+ }
14
+
10
15
  /* uncomment if you want an internal tunnel
11
16
  provider "fly" {
12
17
  useinternaltunnel = true
@@ -49,7 +54,7 @@ resource "fly_machine" "<%= @appName %>Machine" {
49
54
 
50
55
  app = <%= @app.inspect %>
51
56
  name = "<%= @app %>-${each.value}"
52
- image = "quay.io/evl.ms/fullstaq-ruby:<%= @ruby_version %>-jemalloc-slim"
57
+ image = var.image_ref
53
58
 
54
59
  # Scale application resources
55
60
  cpus = 1
@@ -4,26 +4,44 @@ passenger_min_instances 0;
4
4
  passenger_pool_idle_time 300;
5
5
 
6
6
  <% end -%>
7
+ passenger_log_file /dev/stdout;
8
+ passenger_default_user root;
9
+
7
10
  server {
8
- listen 8080;
11
+ listen 8080 default_server;
12
+ listen [::]:8080 default_server;
9
13
  server_name <%= @app %>.fly.dev;
10
14
  root /app/public;
11
15
 
16
+ access_log /dev/stdout;
17
+ error_log /dev/stdout info;
18
+
12
19
  passenger_enabled on;
13
20
  passenger_ruby /usr/lib/fullstaq-ruby/versions/<%= @ruby_version %>-jemalloc/bin/ruby;
14
21
 
15
- <% if @cable -%>
16
- location / {
17
- passenger_app_group_name <%= @app%>;
18
- }
22
+ <% if @anycable -%>
23
+ location /cable {
24
+ proxy_pass http://localhost:8082/cable;
25
+ proxy_http_version 1.1;
26
+ proxy_set_header Upgrade $http_upgrade;
27
+ proxy_set_header Connection "Upgrade";
28
+ proxy_set_header Host $host;
29
+ }
19
30
 
20
- location /cable {
21
- passenger_app_group_name <%= @app%>-cable;
22
- passenger_force_max_concurrent_requests_per_process 0;
23
- }
31
+ <% elsif @cable -%>
32
+ location /cable {
33
+ passenger_app_group_name <%= @app %>-cable;
34
+ passenger_force_max_concurrent_requests_per_process 0;
35
+ }
24
36
 
25
37
  <% end -%>
38
+ location / {
39
+ passenger_app_group_name <%= @app %>;
40
+ passenger_env_var RAILS_SERVE_STATIC_FILES true;
41
+ passenger_env_var RAILS_LOG_TO_STDOUT true;
42
+ }
43
+
26
44
  # Nginx has a default limit of 1 MB for request bodies, which also applies
27
45
  # to file uploads. The following line enables uploads of up to 50 MB:
28
46
  client_max_body_size 50M;
29
- }
47
+ }
data/lib/tasks/fly.rake CHANGED
@@ -37,6 +37,42 @@ namespace :fly do
37
37
  action.generate_ipv6 if @app
38
38
  action.deploy(app, image)
39
39
  end
40
+
41
+ JSON.parse(`fly apps list --json`).each do |info|
42
+ if info['Name'] == app
43
+ 60.times do
44
+ response = Net::HTTP.get_response(URI::HTTPS.build(host: info['Hostname']))
45
+ puts "Server status: #{response.code} #{response.message}"
46
+ break
47
+ rescue Errno::ECONNRESET
48
+ sleep 0.5
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ desc 'dbus daemon - used for IPC'
55
+ task :dbus_deamon do
56
+ IO.write '/var/lib/dbus/machine-id', `hostname`
57
+ mkdir_p '/var/run/dbus'
58
+ sh 'dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address'
59
+ end
60
+
61
+ desc 'Zeroconf/avahi/bonjour discovery'
62
+ task :avahi_publish, [:formation] => :dbus_deamon do |task, args|
63
+ pids = []
64
+ pids << spawn('avahi-daemon')
65
+ sleep 0.1
66
+
67
+ ip = IPSocket.getaddress(Socket.gethostname)
68
+ args[:formation].scan(/([-\w]+)=(\d+)/).each do |name, count|
69
+ next if count.to_i == 0
70
+ pids << spawn("avahi-publish -a -R #{ENV['FLY_REGION']}-#{name}.local #{ip}")
71
+ end
72
+
73
+ at_exit do
74
+ pids.each {|pid| Process.kill 7, pid}
75
+ end
40
76
  end
41
77
  end
42
78
 
@@ -0,0 +1,17 @@
1
+ require 'rack'
2
+ require 'rack/handler/puma'
3
+
4
+ namespace :mock do
5
+ desc 'Mock server - useful for debugging startup issues'
6
+ task :server do
7
+ handler = Rack::Handler::Puma
8
+
9
+ class RackApp
10
+ def call(env)
11
+ [200, {"Content-Type" => "text/plain"}, ["Hello from Fly.io"]]
12
+ end
13
+ end
14
+
15
+ handler.run RackApp.new, Port: ENV['PORT'] || 8080
16
+ end
17
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fly.io-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-07 00:00:00.000000000 Z
11
+ date: 2022-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fly-ruby
@@ -73,11 +73,12 @@ files:
73
73
  - lib/generators/templates/nginx.conf.erb
74
74
  - lib/generators/templates/patches/action_cable.rb
75
75
  - lib/tasks/fly.rake
76
- homepage: https://github.com/rubys/fly-io.rails
76
+ - lib/tasks/mock.rake
77
+ homepage: https://github.com/rubys/fly.io-rails
77
78
  licenses:
78
79
  - Apache-2.0
79
80
  metadata:
80
- homepage_uri: https://github.com/rubys/fly-io.rails
81
+ homepage_uri: https://github.com/rubys/fly.io-rails
81
82
  post_install_message:
82
83
  rdoc_options: []
83
84
  require_paths: