diff --git a/Dockerfile b/Dockerfile index ba704cf11..388e174df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ RUN apt-get update -qq && \ apt-get install --no-install-recommends -y build-essential git libvips pkg-config # Install application gems -COPY Gemfile Gemfile.lock ./ +COPY Gemfile Gemfile.lock .ruby-version ./ RUN bundle install && \ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ bundle exec bootsnap precompile --gemfile @@ -58,5 +58,5 @@ USER rails:rails ENTRYPOINT ["/rails/bin/docker-entrypoint"] # Start the server by default, this can be overwritten at runtime -EXPOSE 3000 -CMD ["./bin/rails", "server"] +EXPOSE 80 443 +CMD ["./bin/thrust", "./bin/rails", "server"] diff --git a/Gemfile b/Gemfile index 55ae610e3..bdda4c9a7 100644 --- a/Gemfile +++ b/Gemfile @@ -13,6 +13,7 @@ gem "turbo-rails" gem "bootsnap", require: false gem "puma", ">= 5.0" gem "sqlite3", ">= 2.0" +gem "thruster", require: false # Features gem "bcrypt", "~> 3.1.7" diff --git a/Gemfile.lock b/Gemfile.lock index ce1c8dc97..f097342ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -274,6 +274,11 @@ GEM stringio (3.1.1) strscan (3.1.0) thor (1.3.2) + thruster (0.1.8) + thruster (0.1.8-aarch64-linux) + thruster (0.1.8-arm64-darwin) + thruster (0.1.8-x86_64-darwin) + thruster (0.1.8-x86_64-linux) timeout (0.4.1) turbo-rails (2.0.7) actionpack (>= 6.0.0) @@ -323,6 +328,7 @@ DEPENDENCIES selenium-webdriver sqlite3 (>= 2.0) stimulus-rails + thruster turbo-rails RUBY VERSION diff --git a/bin/deploy b/bin/deploy new file mode 100755 index 000000000..2417e95b6 --- /dev/null +++ b/bin/deploy @@ -0,0 +1,72 @@ +#!/usr/bin/env ruby +require "bundler/inline" +require "yaml" + +gemfile do + source "https://rubygems.org" + gem "base64" + gem "bcrypt_pbkdf" + gem "ed25519" + gem "sshkit" +end + +include SSHKit::DSL + +config_variant = "-#{ARGV[0]}" if ARGV[0] +config = YAML.load_file("config/deploy#{config_variant}.yml") + +HOSTS = [ config["host"] ] +REMOTE_USER = config["ssh_user"] +DOCKER_IMAGE_NAME = config["image"] +ARCH = config["arch"] || `arch`.strip +GIT_SHA = `git rev-parse HEAD`.strip +DOCKER_TAGGED_IMAGE_NAME = [ DOCKER_IMAGE_NAME, GIT_SHA ].join(":") +PORTS = [ "80:80", "443:443" ] +ENVS = { TLS_DOMAIN: config["ssl_domain"] } +ENV_FILE = ".fizzy-env" + +SSHKit::Backend::Netssh.configure do |ssh| + ssh.connection_timeout = 30 + ssh.ssh_options = { user: REMOTE_USER } +end + +def announcing(message) + puts "\n\e[1;36m#{message}\e[0m" + yield +end + +announcing "Building and pushing image..." do + versioning = "--build-arg APP_VERSION=#{GIT_SHA[...6]} --build-arg GIT_REVISION=#{GIT_SHA}" + + on(:local) do + execute :docker, :build, "-t", DOCKER_TAGGED_IMAGE_NAME, versioning, "--platform", ARCH, "." + execute :docker, :push, DOCKER_TAGGED_IMAGE_NAME + end +end + +announcing "Pulling image on remote..." do + on(HOSTS) do + execute :docker, :pull, DOCKER_TAGGED_IMAGE_NAME + end +end + +announcing "Start container on remote..." do + restart_policy = "--restart unless-stopped" + labels = "--label #{DOCKER_IMAGE_NAME}" + ports = PORTS.map { |p| "-p #{p}" }.join(" ") + envs = ENVS.map { |k, v| "-e #{k}=#{v}" }.join(" ") + volume = "--mount 'type=volume,source=fizzy,target=/rails/storage'" + env_file = "--env-file #{ENV_FILE}" + + on(HOSTS) do + execute :docker, :ps, "-q", "-f", "\"label=#{DOCKER_IMAGE_NAME}\"", "|", "xargs", "-r", "docker", "stop" + execute :docker, :run, "-d", labels, ports, envs, env_file, restart_policy, volume, DOCKER_TAGGED_IMAGE_NAME + end +end + +announcing "Remove stopped containers from remote..." do + on(HOSTS) do + execute :docker, :container, :prune, "-f" + execute :docker, :image, :prune, "-af" + end +end diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint index 67ef49314..4f8ccb547 100755 --- a/bin/docker-entrypoint +++ b/bin/docker-entrypoint @@ -1,7 +1,7 @@ #!/bin/bash -e # If running the rails server then create or migrate existing database -if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then +if [ "${1}" == "./bin/thrust" ] && [ "${2}" == "./bin/rails" ] && [ "${3}" == "server" ]; then ./bin/rails db:prepare fi diff --git a/bin/thrust b/bin/thrust new file mode 100755 index 000000000..705d33a9a --- /dev/null +++ b/bin/thrust @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'thrust' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("thruster", "thrust") diff --git a/config/deploy.yml b/config/deploy.yml new file mode 100644 index 000000000..20d066f16 --- /dev/null +++ b/config/deploy.yml @@ -0,0 +1,5 @@ +image: registry.kevinmcconnell.dev/fizzy +host: fizzy.37signals.works +ssl_domain: fizzy.37signals.works +ssh_user: root +arch: linux/amd64