diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9612375 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,37 @@ +# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. + +# Ignore git directory. +/.git/ + +# Ignore bundler config. +/.bundle + +# Ignore all environment files (except templates). +/.env* +!/.env*.erb + +# Ignore all default key files. +/config/master.key +/config/credentials/*.key + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/.keep + +# Ignore assets. +/node_modules/ +/app/assets/builds/* +!/app/assets/builds/.keep +/public/assets diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77c9aa2..20a3ded 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,7 @@ jobs: bundler-cache: true - name: Run Rubocop run: | - gem install rubocop-rails rubocop-rspec rubocop-performance rubocop-md rubocop-rake rubocop-graphql rubocop-rspec_rails rubocop-sidekiq rubocop-yard + gem install rubocop-rails rubocop-rspec rubocop-performance rubocop-md rubocop-rake rubocop-graphql rubocop-rspec_rails rubocop-sidekiq rubocop-yard rubocop-factory_bot git clone https://gist.github.com/65e21b9e8b0d1db285dcb4fc627b98fa.git .rubocop cp .rubocop/.rubocop.yml .rubocop-ruby.yml git clone https://gist.github.com/14cfa24d53c12bf385871e9b93b95c37.git .rubocop-rspec diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..bdc0550 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,38 @@ +name: Deploy + +on: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + + # Runs when CI passes + workflow_run: + workflows: [CI] + branches: [main] + types: [completed] + +# Allow one concurrent deployment +concurrency: + group: pages + cancel-in-progress: true + +env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + +jobs: + deploy: + name: Deploy to Fly.io + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + - name: Set up flyctl + uses: superfly/flyctl-actions/setup-flyctl@master + - name: Deploy to Fly.io + run: flyctl deploy --remote-only + - name: Notify Bugsnag + uses: psprings/bugsnag-release-action@v0.0.3 + with: + apiKey: ${{ secrets.BUGSNAG_API_KEY }} + appVersion: ${{ github.sha }} + releaseStage: production diff --git a/Capfile b/Capfile deleted file mode 100644 index b5bf683..0000000 --- a/Capfile +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -# Load DSL and set up stages -require "capistrano/setup" - -# Include default deployment tasks -require "capistrano/deploy" - -# Load the SCM plugin appropriate to your project: -# -# require 'capistrano/scm/hg' -# install_plugin Capistrano::SCM::Hg -# or -# require 'capistrano/scm/svn' -# install_plugin Capistrano::SCM::Svn -# or -require "capistrano/scm/git" -install_plugin Capistrano::SCM::Git - -require "capistrano/scm/git-with-submodules" -install_plugin Capistrano::SCM::Git::WithSubmodules - -# Include tasks from other gems included in your Gemfile -# -# For documentation on these, see for example: -# -# https://github.com/capistrano/rvm -# https://github.com/capistrano/rbenv -# https://github.com/capistrano/chruby -# https://github.com/capistrano/bundler -# https://github.com/capistrano/rails -# https://github.com/capistrano/passenger -# -require "capistrano/rvm" -require "capistrano/bundler" -require "capistrano/rails/migrations" -require "bugsnag-capistrano" - -# Load custom tasks from `lib/capistrano/tasks` if you have any defined -Dir.glob("lib/capistrano/tasks/*.rake").each { _1 import _1 } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f80cb97 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,61 @@ +# syntax = docker/dockerfile:1 + +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile +ARG RUBY_VERSION=3.3.3 +FROM ruby:$RUBY_VERSION-slim as base + +LABEL fly_launch_runtime="rails" + +# Rails app lives here +WORKDIR /rails + +# Set production environment +ENV BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development:test" \ + RAILS_ENV="production" + +# Update gems and bundler +RUN gem update --system --no-document && \ + gem install -N bundler + + +# 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 --no-install-recommends -y build-essential libpq-dev libyaml-dev git + +# Install application gems +COPY --link Gemfile Gemfile.lock ./ +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git + +# Copy application code +COPY --link . . + + +# 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 postgresql-client && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Copy built artifacts: gems, application +COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" +COPY --from=build /rails /rails + +# 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 && \ + chown -R 1000:1000 db log storage tmp +USER 1000:1000 + +# Entrypoint sets up the container. +ENTRYPOINT ["/rails/bin/docker-entrypoint"] + +# Start the server by default, this can be overwritten at runtime +EXPOSE 3000 diff --git a/Gemfile b/Gemfile index 3bf6995..bf29f1c 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.3.3" +gem "net-pop", github: "ruby/net-pop" # 3.3.3 hack fix # CORE gem "puma" @@ -30,17 +31,8 @@ group :development do # ERRORS gem "binding_of_caller" - # DEPLOYMENT - gem "bcrypt_pbkdf", require: false - gem "bugsnag-capistrano", require: false - gem "capistrano", require: false - gem "capistrano-bundler", require: false - gem "capistrano-git-with-submodules", require: false - gem "capistrano-nvm", require: false - gem "capistrano-rails", require: false - gem "capistrano-rvm", require: false - gem "capistrano-sidekiq", require: false - gem "ed25519", require: false + # FLY.IO + gem "dockerfile-rails" end group :doc do diff --git a/Gemfile.lock b/Gemfile.lock index cc75964..a2f6e04 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: https://github.com/ruby/net-pop.git + revision: e8d0afe2773b9eb6a23c39e9e437f6fc0fc7c733 + specs: + net-pop (0.1.2) + net-protocol + GEM remote: https://rubygems.org/ specs: @@ -77,40 +84,14 @@ GEM tzinfo (~> 2.0) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) - airbrussh (1.5.2) - sshkit (>= 1.6.1, != 1.7.0) base64 (0.2.0) bcrypt (3.1.20) - bcrypt_pbkdf (1.1.1) - bcrypt_pbkdf (1.1.1-arm64-darwin) bigdecimal (3.1.8) binding_of_caller (1.0.1) debug_inspector (>= 1.2.0) bugsnag (6.27.1) concurrent-ruby (~> 1.0) - bugsnag-capistrano (2.1.0) builder (3.3.0) - capistrano (3.18.1) - airbrussh (>= 1.0.0) - i18n - rake (>= 10.0.0) - sshkit (>= 1.9.0) - capistrano-bundler (2.1.0) - capistrano (~> 3.1) - capistrano-git-with-submodules (2.0.6) - capistrano (~> 3.7) - capistrano-nvm (0.0.7) - capistrano (~> 3.1) - capistrano-rails (1.6.3) - capistrano (~> 3.1) - capistrano-bundler (>= 1.1, < 3) - capistrano-rvm (0.1.2) - capistrano (~> 3.0) - sshkit (~> 1.2) - capistrano-sidekiq (2.3.1) - capistrano (>= 3.9.0) - capistrano-bundler - sidekiq (>= 6.0) concurrent-ruby (1.3.3) connection_pool (2.4.1) crack (1.0.0) @@ -135,6 +116,8 @@ GEM devise (~> 4.0) warden-jwt_auth (~> 0.8) diff-lcs (1.5.1) + dockerfile-rails (1.6.16) + rails (>= 3.0.0) drb (2.2.1) dry-auto_inject (1.0.1) dry-core (~> 1.0) @@ -145,7 +128,6 @@ GEM dry-core (1.0.1) concurrent-ruby (~> 1.0) zeitwerk (~> 2.6) - ed25519 (1.3.0) erubi (1.13.0) factory_bot (6.4.6) activesupport (>= 5.0.0) @@ -183,17 +165,13 @@ GEM net-imap (0.4.13) date net-protocol - net-pop (0.1.2) net-protocol (0.2.2) timeout - net-scp (4.0.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.0) net-protocol - net-ssh (7.2.3) nio4r (2.7.3) + nokogiri (1.16.6-aarch64-linux) + racc (~> 1.4) nokogiri (1.16.6-arm64-darwin) racc (~> 1.4) nokogiri (1.16.6-x86_64-linux) @@ -286,12 +264,6 @@ GEM connection_pool (>= 2.3.0) rack (>= 2.2.4) redis-client (>= 0.19.0) - sshkit (1.22.2) - base64 - mutex_m - net-scp (>= 1.1.2) - net-sftp (>= 2.1.2) - net-ssh (>= 2.8.0) stringio (3.1.1) strscan (3.1.0) thor (1.3.1) @@ -317,30 +289,23 @@ GEM zeitwerk (2.6.16) PLATFORMS + aarch64-linux arm64-darwin-22 arm64-darwin-23 x86_64-linux DEPENDENCIES - bcrypt_pbkdf binding_of_caller bugsnag - bugsnag-capistrano - capistrano - capistrano-bundler - capistrano-git-with-submodules - capistrano-nvm - capistrano-rails - capistrano-rvm - capistrano-sidekiq database_cleaner devise devise-jwt - ed25519 + dockerfile-rails factory_bot_rails ffaker jbuilder json_expressions + net-pop! pg puma rack-cors diff --git a/README.md b/README.md index fa8ab8d..07bded6 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ End-to-end testing is documented in the readme for the front-end. #### Deployment -The back-end is deployed using Capistrano. The front-end is a static site that -is compiled and deployed using a simple shell script with `rsync`. +The back-end and front-end are both deployed using Fly.io. A GitHub Action, +defined in the `deploy.yml` workflow, runs after CI is complete. ## Architecture diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100755 index 0000000..9dd92e3 --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,5 @@ +#!/bin/bash -e + +# Add any container initialization steps here + +exec "${@}" diff --git a/config/deploy.rb b/config/deploy.rb deleted file mode 100644 index 4c3cdc8..0000000 --- a/config/deploy.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require File.expand_path("./environment", __dir__) - -set :application, "flyweight" -set :repo_url, "https://github.com/FlyWeight-org/Backend.git" - -set :branch, "main" - -set :pty, false - -# Default deploy_to directory is /var/www/my_app_name -set :deploy_to, "/var/www/app.flyweight.org" - -append :linked_files, "config/master.key" - -set :default_env, {path: "/usr/local/nvm/versions/node/v16.13.0/bin:$PATH"} - -# Default value for linked_dirs is [] -append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", - "node_modules", "public/packs", "public/assets" - -set :rvm_ruby_version, "3.3.2@#{fetch :application}" - -set :sidekiq_config, "config/sidekiq.yml" - -set :bugsnag_api_key, Rails.application.credentials.bugsnag_api_key - -namespace :deploy do - task :restart do - on roles(:app) do - sudo "systemctl", "restart", "rails-flyweight" - end - end -end - -namespace :sidekiq do - task :restart do - on roles(:app) do - sudo "systemctl", "restart", "sidekiq-flyweight" - end - end -end - -after "deploy:finished", "deploy:restart" -after "deploy:finished", "sidekiq:restart" diff --git a/config/deploy/production.rb b/config/deploy/production.rb deleted file mode 100644 index 04c7e3f..0000000 --- a/config/deploy/production.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -server "app.flyweight.org", - user: "deploy", - roles: %w[app db web] diff --git a/config/dockerfile.yml b/config/dockerfile.yml new file mode 100644 index 0000000..2568cc9 --- /dev/null +++ b/config/dockerfile.yml @@ -0,0 +1,9 @@ +# generated by dockerfile-rails + +--- +options: + label: + fly_launch_runtime: rails + postgresql: true + prepare: false + redis: true diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index ab57d42..bfe5a2e 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -10,11 +10,6 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins Rails.application.config.urls.frontend - - resource "*", - headers: %w[Authorization], - methods: :any, - expose: %w[Authorization], - max_age: 600 + resource "*", methods: :any, credentials: true end end diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..ecfc6ba --- /dev/null +++ b/fly.toml @@ -0,0 +1,34 @@ +# fly.toml app configuration file generated for flyweight-app on 2024-06-19T11:32:12-07:00 +# +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# + +app = 'flyweight-app' +primary_region = 'sjc' +console_command = '/rails/bin/rails console' + +[build] + +[deploy] + release_command = './bin/rails db:prepare' + +[processes] + app = './bin/rails server' + sidekiq = 'bundle exec sidekiq' + +[http_service] + internal_port = 3000 + force_https = true + auto_stop_machines = true + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + +[[vm]] + memory = '1gb' + cpu_kind = 'shared' + cpus = 1 + +[[statics]] + guest_path = '/rails/public' + url_prefix = '/' diff --git a/log/.gitignore b/log/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/log/.gitignore @@ -0,0 +1 @@ +* diff --git a/storage/.gitignore b/storage/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/storage/.gitignore @@ -0,0 +1 @@ +*