Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flyio #34

Merged
merged 3 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20.12.2
20.12.2
4 changes: 1 addition & 3 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ def route_component(route)

# https://www.leighhalliday.com/ruby-metaprogramming-method-missing
sig do
params(method_name: Symbol,
args: T::Array[T.nilable(T.any(T::Hash[T.untyped, T.untyped],
T.proc.returns(T::Hash[T.untyped, T.untyped])))]).void
params(method_name: Symbol, args: T.untyped).void
end
def method_missing(method_name, *args)
mn = method_name.to_s
Expand Down
2 changes: 1 addition & 1 deletion app/models/push_notification_subscription.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class PushNotificationSubscription < ApplicationRecord

scope :active, -> { where(subscribed: true) }

sig { params(message: String).returns(T.untyped) }
sig { params(message: T::Hash[String, T.untyped]).returns(T.untyped) }
def send_web_push_notification(message)
WebPush.payload_send(
message: JSON.generate(message),
Expand Down
6 changes: 6 additions & 0 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,10 @@
// Make the VAPID public key available to the client as a string
window.VAPID_PUBLIC_KEY = "<%= ENV['VAPID_PUBLIC_KEY'].delete('=') %>"
</script>
<script>
// Hit the backend every 4.95 minutes to ensure uptime while a user is on the page
window.setTimeout(() => {
fetch("/up").then(() => null).catch(console.error)
}, 1000 * 60 * 4.95)
</script>
</html>
10 changes: 9 additions & 1 deletion bin/docker-entrypoint
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
#!/bin/bash -e

./bin/rake sway:volume_setup

# If running the rails server then create or migrate existing database
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
echo "####################################################"
echo "docker-entrypoint -> Running rails db:migrate"
echo "####################################################"
# db:prepare - https://www.bigbinary.com/blog/rails-6-adds-rails-db-prepare-to-migrate-or-setup-a-database
./bin/rails db:prepare
./bin/rails db:migrate

echo "####################################################"
echo "docker-entrypoint -> Running rails db:seed"
echo "####################################################"
# Ensure seeds are run
./bin/rails db:seed
fi
Expand Down
2 changes: 1 addition & 1 deletion config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ class Application < Rails::Application
config.time_zone = 'UTC'
# config.eager_load_paths << Rails.root.join("extras")

config.force_ssl = true
config.force_ssl = false
end
end
1 change: 1 addition & 0 deletions config/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ test:
production:
<<: *default
database: storage/production.sqlite3
# database: storage/prodswaysqlite/production.sqlite3

# Postgres
# production:
Expand Down
9 changes: 7 additions & 2 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
require "active_support/core_ext/integer/time"

Rails.application.configure do
config.active_record.sqlite3_production_warning = false

# Settings specified here will take precedence over those in config/application.rb.

# Code is not reloaded between requests.
Expand Down Expand Up @@ -50,7 +52,7 @@
# config.assume_ssl = true

# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
config.force_ssl = false

# Log to STDOUT by default
config.logger = ActiveSupport::Logger.new(STDOUT)
Expand Down Expand Up @@ -96,7 +98,10 @@
# https://guides.rubyonrails.org/security.html#dns-rebinding-and-host-header-attacks
config.hosts = [
# "example.com", # Allow requests from example.com
/.*\.sway\.vote/ # Allow requests from subdomains like `www.example.com`
/.*\.sway\.vote/, # Allow requests from subdomains like `www.example.com`
/.*\.fly\.dev/, # Allow requests from subdomains like `www.example.com`
'localhost',
'127.0.0.1'
]
# Skip DNS rebinding protection for the default health check endpoint.
config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
Expand Down
31 changes: 17 additions & 14 deletions config/puma.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# typed: false

# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma"s configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.

puts ""
puts "##################################################################"
puts ""
puts ''
puts '##################################################################'
puts ''
puts "Puma starting with RAILS_ENV = #{ENV.fetch('RAILS_ENV')}"
puts ""
puts "##################################################################"
puts ""
puts ''
puts '##################################################################'
puts ''

# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
Expand All @@ -32,14 +33,16 @@
worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development'

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
# port ENV.fetch("PORT") { 3000 }

ssl_bind '0.0.0.0', ENV.fetch('PORT', 3000), {
key: 'config/ssl/key.pem',
cert: 'config/ssl/cert.pem',
verify_mode: 'none'
}
# if ENV.fetch('RAILS_ENV', 'development') == 'development'

if ENV['RAILS_ENV'] == 'production'
port ENV.fetch('PORT') { 3000 }
else
ssl_bind '0.0.0.0', ENV.fetch('PORT', 3000), {
key: 'config/ssl/key.pem',
cert: 'config/ssl/cert.pem',
verify_mode: 'none'
}
end

# Specifies the `environment` that Puma will run in.
environment ENV.fetch('RAILS_ENV') { 'development' }
Expand Down
4 changes: 3 additions & 1 deletion docker/dockerfiles/production.dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
ARG RUBY_VERSION=3.3.1
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim AS base

LABEL fly_launch_runtime="rails"

# Rails app lives here
WORKDIR /rails

Expand Down Expand Up @@ -74,4 +76,4 @@ ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start the server by default, this can be overwritten at runtime
EXPOSE 3000

CMD ["./bin/rails", "server", "-u", "puma"]
CMD ["./bin/rails", "server", "-u", "puma", "-b", "0.0.0.0", "-p", "3000", "-e", "production"]
8 changes: 8 additions & 0 deletions docker/dockerfiles/production.dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# Ignore git directory.
/.git/
/.github/

# Ignore vscode
/.vscode
Expand All @@ -20,6 +21,7 @@
/config/master.key
/config/credentials/*.key
/config/keys/
/config/ssl

# Ignore all logfiles and tempfiles.
/log/*
Expand All @@ -41,11 +43,15 @@
# Ignore terraform
/tf/

# Ignore litestream
/litestream/

# Ignore assets.
/node_modules/
/app/assets/builds/
#/app/assets/images/
#/public/*
/public/vite-test/

# Don't need views except layouts
/views
Expand Down Expand Up @@ -74,3 +80,5 @@

# DO ignore geojson files
**/**/.*geojson

fly.toml
58 changes: 58 additions & 0 deletions fly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# fly.toml app configuration file generated for sway on 2024-06-07T10:24:01-04:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'sway'
primary_region = 'mia'
console_command = '/rails/bin/rails console'

[build]
image = 'ghcr.io/plebeian-technology/sway:latest'
#dockerfile = "docker/dockerfiles/production.dockerfile"
#ignorefile = "docker/dockerfiles/production.dockerfile.dockerignore"

[[mounts]]
source = 'prodswaysqlite'
destination = '/storage'

[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 1
max_machines_running = 3

[http_service.concurrency]
type = "requests"
soft_limit = 200
hard_limit = 250

[[http_service.checks]]
interval = '30s'
timeout = '5s'
grace_period = '10s'
method = 'GET'
path = '/up'

[checks]
[checks.status]
port = 3000
type = 'http'
interval = '10s'
timeout = '2s'
grace_period = '5s'
method = 'GET'
path = '/up'
protocol = 'http'
tls_skip_verify = false

[checks.status.headers]
X-Forwarded-Proto = 'https'

[[vm]]
#memory = '256mb'
memory = '1gb'
cpu_kind = 'shared'
cpus = 1
33 changes: 32 additions & 1 deletion lib/sway_google_cloud_storage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,40 @@ def generate_put_signed_url_v4(bucket_name:, file_name:, content_type:)
)
end

def upload_file(bucket_name:, bucket_file_path:, local_file_path:)
return unless bucket_name && bucket_file_path && local_file_path

bucket = storage.bucket bucket_name, skip_lookup: true
bucket.create_file local_file_path, bucket_file_path
end

def download_file(bucket_name:, bucket_file_path:, local_file_path:)
return unless bucket_name && bucket_file_path && local_file_path

bucket = storage.bucket bucket_name, skip_lookup: true

file = bucket.file bucket_file_path

FileUtils.mkdir_p(local_file_path.split('/')[0..-2].join('/'))
file.download local_file_path
end

def download_directory(bucket_name:, bucket_directory_name:, local_directory_name:)
return unless bucket_name && bucket_directory_name && local_directory_name

bucket = storage.bucket bucket_name, skip_lookup: true

dir = bucket.files prefix: "#{bucket_directory_name}/"

dir.all do |f|
FileUtils.mkdir_p("#{local_directory_name}/#{f.name.split('/')[0..-2].join('/')}")
f.download "#{local_directory_name}/#{f.name}"
end
end

def delete_file(bucket_name:, file_name:)
return unless bucket_name && file_name
return if file_name.starts_with? "https://"
return if file_name.starts_with? 'https://'

bucket = storage.bucket bucket_name, skip_lookup: true
file = bucket.file file_name
Expand Down
25 changes: 25 additions & 0 deletions lib/tasks/fly.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# https://fly.io/docs/rails/advanced-guides/sqlite3/

# commands used to deploy a Rails application
namespace :fly do
# BUILD step:
# - changes to the filesystem made here DO get deployed
# - NO access to secrets, volumes, databases
# - Failures here prevent deployment
task build: 'assets:precompile'

# RELEASE step:
# - changes to the filesystem made here are DISCARDED
# - full access to secrets, databases
# - failures here prevent deployment
task :release

# SERVER step:
# - changes to the filesystem made here are deployed
# - full access to secrets, databases
# - failures here result in VM being stated, shutdown, and rolled back
# to last successful deploy (if any).
task server: 'db:migrate' do
sh 'bin/rails server'
end
end
21 changes: 21 additions & 0 deletions lib/tasks/sway.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require_relative '../sway_google_cloud_storage'

namespace :sway do
include SwayGoogleCloudStorage

desc 'Sets up a remote volume with files downloaded from a Google Cloud bucket'
task volume_setup: :environment do
download_directory(bucket_name: 'sway-sqlite', bucket_directory_name: 'seeds', local_directory_name: 'storage')
download_directory(bucket_name: 'sway-sqlite', bucket_directory_name: 'geojson', local_directory_name: 'storage')

if File.exist? 'storage/production.sqlite3'
puts 'Uploading production.db to google storage as backup.'
upload_file(bucket_name: 'sway-sqlite', bucket_file_path: 'production.sqlite3',
local_file_path: 'storage/production.sqlite3')
else
puts 'Getting production.db from google storage backup.'
download_file(bucket_name: 'sway-sqlite', bucket_file_path: 'production.sqlite3',
local_file_path: 'storage/production.sqlite3')
end
end
end
27 changes: 20 additions & 7 deletions scripts/deploy.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env zsh

export $(cat .env.github | xargs)

Expand All @@ -16,13 +16,26 @@ export $(cat .env.github | xargs)
SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:clobber
RAILS_ENV=production SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:clobber

./litestream/replicate.sh
if [[ "$1" = "google" ]]; then

gcloud storage cp --recursive $(pwd)/storage/geojson gs://sway-sqlite/
./litestream/replicate.sh

gcloud storage cp --recursive $(pwd)/storage/seeds/data gs://sway-sqlite/seeds/
gcloud storage cp --recursive $(pwd)/storage/geojson gs://sway-sqlite/

# Cloud Run requires AMD64 images
docker buildx build . -f docker/dockerfiles/production.dockerfile --platform linux/amd64 -t us-central1-docker.pkg.dev/sway-421916/sway/sway:latest --push --compress
gcloud storage cp --recursive $(pwd)/storage/seeds/data gs://sway-sqlite/seeds/

gcloud run deploy sway --project=sway-421916 --region=us-central1 --image=us-central1-docker.pkg.dev/sway-421916/sway/sway:latest --revision-suffix=${1}
# Cloud Run requires AMD64 images
docker buildx build . -f docker/dockerfiles/production.dockerfile --platform linux/amd64 -t us-central1-docker.pkg.dev/sway-421916/sway/sway:latest --push --compress

gcloud run deploy sway --project=sway-421916 --region=us-central1 --image=us-central1-docker.pkg.dev/sway-421916/sway/sway:latest --revision-suffix=${1}

elif [[ "$1" = "flyio" ]]; then

echo $GITHUB_ACCESS_TOKEN | docker login ghcr.io -u dcordz --password-stdin

docker buildx build . -f docker/dockerfiles/production.dockerfile --platform linux/amd64 -t ghcr.io/plebeian-technology/sway:latest --compress --push

# docker buildx build . -f docker/dockerfiles/production.dockerfile --platform linux/amd64 -t sway-prod:latest --compress

fly deploy
fi
Loading
Loading