diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..325bfc0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. + +# Ignore git directory. +/.git/ +/.gitignore + +# Ignore bundler config. +/.bundle + +# Ignore all environment files. +/.env* + +# 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 + +# Ignore CI service files. +/.github + +# Ignore Kamal files. +/config/deploy*.yml +/.kamal + +# Ignore development files +/.devcontainer + +# Ignore Docker-related files +/.dockerignore +/Dockerfile* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d36d2b5..795c628 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,15 @@ name: CI on: push: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + packages: write jobs: lint_and_scan_ruby: @@ -114,3 +123,61 @@ jobs: --order random \ --format documentation \ --format RSpec::Github::Formatter + + deploy: + if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' + needs: [lint_and_scan_ruby, test] + runs-on: ubuntu-latest + environment: production + concurrency: + group: deploy-production + cancel-in-progress: false + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v7 + with: + context: . + push: true + tags: | + ghcr.io/convus/convus_webapp:${{ github.sha }} + ghcr.io/convus/convus_webapp:latest + labels: service=convus_webapp + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up SSH + uses: webfactory/ssh-agent@v0.10.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Deploy with Kamal + run: | + cp .kamal/secrets-ci .kamal/secrets + bundle exec kamal deploy --skip-push + env: + KAMAL_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + HONEYBADGER_API_KEY: ${{ secrets.HONEYBADGER_API_KEY }} + DEVISE_SECRET_KEY: ${{ secrets.DEVISE_SECRET_KEY }} + DEVISE_PEPPER: ${{ secrets.DEVISE_PEPPER }} diff --git a/.gitignore b/.gitignore index e6b9fc3..33678a1 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ yarn-error.log /node_modules /app/assets/builds/* !/app/assets/builds/.keep +esbuild_error # Ignore master key for decrypting credentials and more. /config/master.key @@ -47,3 +48,7 @@ yarn-error.log # Workspace ID assigned by bin/workspace_setup .workspace_id + +# Ignore Kamal secrets. +/.kamal/secrets* +!/.kamal/secrets-ci diff --git a/.kamal/hooks/post-deploy b/.kamal/hooks/post-deploy new file mode 100755 index 0000000..7b3d3cf --- /dev/null +++ b/.kamal/hooks/post-deploy @@ -0,0 +1,7 @@ +#!/bin/sh + +bundle exec honeybadger deploy \ + --repository https://github.com/convus/convus_webapp \ + --revision $KAMAL_VERSION \ + --environment production \ + --user $KAMAL_PERFORMER diff --git a/.kamal/secrets-ci b/.kamal/secrets-ci new file mode 100644 index 0000000..c2b7478 --- /dev/null +++ b/.kamal/secrets-ci @@ -0,0 +1,9 @@ +# CI secrets file — all values come from GitHub Actions env vars. +# Kamal's Dotenv parser requires explicit assignments (no shell conditionals). +KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD +RAILS_MASTER_KEY=$RAILS_MASTER_KEY +SECRET_KEY_BASE=$SECRET_KEY_BASE +HONEYBADGER_API_KEY=$HONEYBADGER_API_KEY +POSTGRES_PASSWORD=$POSTGRES_PASSWORD +DEVISE_SECRET_KEY=$DEVISE_SECRET_KEY +DEVISE_PEPPER=$DEVISE_PEPPER diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..357c3fc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,74 @@ +# syntax=docker/dockerfile:1 +# check=error=true + +# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: +# docker build -t convus_webapp . +# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name convus_webapp convus_webapp + +# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html + +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version +ARG RUBY_VERSION=4.0.2 +FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base + +# Rails app lives here +WORKDIR /rails + +# Install base packages +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \ + ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Set production environment variables and enable jemalloc for reduced memory usage and latency. +ENV RAILS_ENV="production" \ + BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development" \ + LD_PRELOAD="/usr/local/lib/libjemalloc.so" + +# 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 git libpq-dev libyaml-dev pkg-config && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Install application gems +COPY vendor/* ./vendor/ +COPY Gemfile Gemfile.lock ./ + +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + # -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495 + bundle exec bootsnap precompile -j 1 --gemfile + +# Copy application code +COPY . . + +# Precompile bootsnap code for faster boot times. +# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495 +RUN bundle exec bootsnap precompile -j 1 app/ lib/ + +# Precompiling assets for production without requiring secret RAILS_MASTER_KEY +RUN SECRET_KEY_BASE_DUMMY=1 REDIS_URL="redis://localhost:6379" ./bin/rails assets:precompile + +# Final stage for app image +FROM base + +# 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 +USER 1000:1000 + +# Copy built artifacts: gems, application +COPY --chown=rails:rails --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" +COPY --chown=rails:rails --from=build /rails /rails + +# Entrypoint prepares the database. +ENTRYPOINT ["/rails/bin/docker-entrypoint"] + +# Start server via Thruster by default, this can be overwritten at runtime +EXPOSE 80 +CMD ["./bin/thrust", "./bin/rails", "server"] diff --git a/Gemfile b/Gemfile index 9e3b0f8..e18b5ab 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,8 @@ ruby "4.0.6" gem "rails" gem "puma" # Use Puma as the app server +gem "thruster", require: false # Asset compression and caching for Puma +gem "kamal", require: false # Deploy with Kamal gem "rack-cors" # Make cors requests # database stuff diff --git a/Gemfile.lock b/Gemfile.lock index 965f9a9..e897088 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - action_text-trix (2.1.19) + action_text-trix (2.1.15) railties actioncable (8.1.3) actionpack (= 8.1.3) @@ -75,23 +75,33 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.9.0) + addressable (2.8.8) public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) attr_extras (7.1.0) - axe-core-api (4.12.0) + axe-core-api (4.11.1) dumb_delegator - axe-core-rspec (4.12.0) - axe-core-api (= 4.12.0) + ostruct + virtus + axe-core-rspec (4.11.1) + axe-core-api (= 4.11.1) + dumb_delegator + ostruct + virtus + axiom-types (0.1.1) + descendants_tracker (~> 0.0.4) + ice_nine (~> 0.11.0) + thread_safe (~> 0.3, >= 0.3.1) base64 (0.3.0) bcrypt (3.1.22) - bigdecimal (4.1.2) + bcrypt_pbkdf (1.1.2) + bigdecimal (4.1.1) binxtils (0.5.2) functionable loofah rails rails-html-sanitizer - bootsnap (1.24.6) + bootsnap (1.19.0) msgpack (~> 1.2) brakeman (8.0.5) racc @@ -105,69 +115,66 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - chartkick (5.2.1) + chartkick (4.2.1) childprocess (5.1.0) logger (~> 1.5) coderay (1.1.3) - commonmarker (2.9.0-arm64-darwin) - commonmarker (2.9.0-x86_64-linux) - concurrent-ruby (1.3.8) - connection_pool (3.0.2) - crack (1.0.1) - bigdecimal + coercible (1.0.0) + descendants_tracker (~> 0.0.1) + commonmarker (2.7.0-arm64-darwin) + commonmarker (2.7.0-x86_64-linux) + concurrent-ruby (1.3.6) + connection_pool (2.5.5) + crack (0.4.5) rexml - crass (1.0.7) - css_parser (3.0.0) + crass (1.0.6) + css_parser (1.14.0) addressable - ssrf_filter (~> 1.5) - csv (3.3.6) + csv (3.3.5) date (3.5.1) debug (1.11.1) irb (~> 1.10) reline (>= 0.3.8) - devise (5.0.4) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) + devise (5.0.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 7.0) responders warden (~> 1.2.3) diff-lcs (1.6.2) - dotenv (3.2.0) - dotenv-rails (3.2.0) - dotenv (= 3.2.0) - railties (>= 6.1) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) + railties (>= 3.2) drb (2.2.3) dumb_delegator (1.1.0) - erb (6.0.6) + ed25519 (1.4.0) + erb (6.0.2) erubi (1.13.1) - factory_bot (6.6.0) - activesupport (>= 6.1.0) - factory_bot_rails (6.5.1) - factory_bot (~> 6.5) - railties (>= 6.1.0) - faraday (2.14.3) - faraday-net_http (>= 2.0, < 3.5) - json - logger - faraday-net_http (3.4.4) - net-http (~> 0.5) + factory_bot (6.2.1) + activesupport (>= 5.0.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) + railties (>= 5.0.0) + faraday (2.7.1) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) fast_blank (1.0.1) - ffi (1.17.4-arm64-darwin) - ffi (1.17.4-x86_64-linux-gnu) + ffi (1.15.5) flamegraph (0.9.5) - foreman (0.90.0) - thor (~> 1.4) - formatador (1.2.3) - reline + foreman (0.87.2) + formatador (1.1.0) functionable (1.3.0) - globalid (1.4.0) + globalid (1.3.0) activesupport (>= 6.1) - groupdate (6.8.0) - activesupport (>= 7.2) - guard (2.20.2) + groupdate (6.1.0) + activesupport (>= 5.2) + guard (2.18.0) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) - logger (~> 1.6) lumberjack (>= 1.0.12, < 2.0) nenv (~> 0.1) notiffany (~> 0.0) @@ -179,34 +186,44 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - hamlit (4.0.0) + hamlit (3.0.3) temple (>= 0.8.2) thor tilt - hashdiff (1.2.1) - honeybadger (6.9.1) + hashdiff (1.0.1) + honeybadger (6.5.2) logger ostruct - hotwire-livereload (2.1.1) - actioncable (>= 7.0.0) + hotwire-livereload (1.2.3) listen (>= 3.0.0) - railties (>= 7.0.0) - htmlbeautifier (1.4.3) + rails (>= 6.0.0) + htmlbeautifier (1.4.2) htmlentities (4.3.4) - i18n (1.15.2) + i18n (1.14.8) concurrent-ruby (~> 1.0) - importmap-rails (2.2.3) + ice_nine (0.11.2) + importmap-rails (1.1.5) actionpack (>= 6.0.0) - activesupport (>= 6.0.0) railties (>= 6.0.0) io-console (0.8.2) - irb (1.18.0) + irb (1.17.0) pp (>= 0.6.0) prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.21.1) - language_server-protocol (3.17.0.6) + json (2.19.3) + kamal (1.9.3) + activesupport (>= 7.0) + base64 (~> 0.2) + bcrypt_pbkdf (~> 1.0) + concurrent-ruby (~> 1.2) + dotenv (~> 2.8) + ed25519 (~> 1.2) + net-ssh (~> 7.0) + sshkit (>= 1.23.0, < 2.0) + thor (~> 1.2) + zeitwerk (~> 2.5) + language_server-protocol (3.17.0.5) launchy (3.1.1) addressable (~> 2.8) childprocess (~> 5.0) @@ -214,18 +231,17 @@ GEM letter_opener (1.10.0) launchy (>= 2.2, < 4) lint_roller (1.1.0) - listen (3.10.0) - logger - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) + listen (3.0.8) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) logger (1.7.0) - lograge (0.15.0) + lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) request_store (~> 1.0) logstash-event (1.2.02) - loofah (2.25.2) + loofah (2.25.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) lookbook (2.3.14) @@ -240,38 +256,42 @@ GEM view_component (>= 2.0) yard (~> 0.9) zeitwerk (~> 2.5) - lumberjack (1.4.2) - mail (2.9.1) + lumberjack (1.2.8) + mail (2.9.0) logger mini_mime (>= 0.1.1) net-imap net-pop net-smtp - marcel (1.2.1) + marcel (1.1.0) matrix (0.4.3) method_source (1.1.0) mini_mime (1.1.5) - minitest (6.0.6) + minitest (6.0.3) drb (~> 2.0) prism (~> 1.5) - msgpack (1.8.4) - mustermann (3.1.1) + msgpack (1.8.0) + mustermann (3.0.4) + ruby2_keywords (~> 0.0.1) nenv (0.3.0) - net-http (0.9.1) - uri (>= 0.11.1) - net-imap (0.6.6) + net-imap (0.5.12) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout + net-scp (4.1.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.1) net-protocol + net-ssh (7.3.3) nio4r (2.7.5) - nokogiri (1.19.4-arm64-darwin) + nokogiri (1.19.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.19.4-x86_64-linux-gnu) + nokogiri (1.19.2-x86_64-linux-gnu) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) @@ -284,21 +304,21 @@ GEM json uri yaml - parallel (2.1.0) + parallel (1.28.0) parallel_tests (4.10.1) parallel - parser (3.3.12.0) + parser (3.3.11.1) ast (~> 2.4.1) racc patience_diff (1.2.0) optimist (~> 3.0) pg (1.6.3-arm64-darwin) pg (1.6.3-x86_64-linux) - pp (0.6.4) + pp (0.6.3) prettyprint - premailer (1.29.0) + premailer (1.19.0) addressable - css_parser (>= 1.19.0) + css_parser (>= 1.12.0) htmlentities (>= 4.0.0) premailer-rails (1.12.0) actionmailer (>= 3) @@ -306,23 +326,25 @@ GEM premailer (~> 1.7, >= 1.7.9) prettyprint (0.2.0) prism (1.9.0) - propshaft (1.3.2) + propshaft (0.6.4) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack - pry (0.16.0) + railties (>= 7.0.0) + pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) - reline (>= 0.6.0) - public_suffix (7.0.5) - puma (8.0.2) + psych (5.3.1) + date + stringio + public_suffix (7.0.0) + puma (7.2.0) nio4r (~> 2.0) racc (1.8.1) rack (3.2.6) - rack-cors (3.0.0) - logger - rack (>= 3.0.14) - rack-mini-profiler (4.0.1) + rack-cors (2.0.0) + rack (>= 2.0.0) + rack-mini-profiler (3.0.0) rack (>= 1.2.0) rack-protection (4.2.1) base64 (>= 0.1.0) @@ -357,8 +379,8 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.7.1) - loofah (~> 2.25, >= 2.25.2) + rails-html-sanitizer (1.7.0) + loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) railties (8.1.3) actionpack (= 8.1.3) @@ -370,26 +392,20 @@ GEM tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.4.2) + rake (13.3.1) rb-fsevent (0.11.2) - rb-inotify (0.11.1) + rb-inotify (0.10.1) ffi (~> 1.0) - rbs (4.1.0) - logger - prism (>= 1.6.0) - tsort - rdoc (8.0.0) + rdoc (7.2.0) erb - prism (>= 1.6.0) - rbs (>= 4.0.0) + psych (>= 4.0.0) tsort redcarpet (3.6.1) - redis (5.4.1) - redis-client (>= 0.22.0) - redis-client (0.30.1) + redis (4.8.0) + redis-client (0.26.2) connection_pool - redlock (2.1.0) - redis-client (>= 0.14.1, < 1.0.0) + redlock (2.0.1) + redis-client regexp_parser (2.12.0) reline (0.6.3) io-console (~> 0.5) @@ -429,18 +445,18 @@ GEM rspec-support (3.13.7) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.88.2) + rubocop (1.84.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) - parallel (>= 1.10) + parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.50.0) + rubocop-ast (1.49.1) parser (>= 3.3.7.2) prism (~> 1.7) rubocop-performance (1.26.1) @@ -448,21 +464,22 @@ GEM rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (1.13.0) - rubyzip (3.4.1) + ruby2_keywords (0.0.5) + rubyzip (3.2.2) securerandom (0.4.1) - selenium-webdriver (4.46.0) + selenium-webdriver (4.43.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 4.0) websocket (~> 1.0) shellany (0.0.1) - sidekiq (8.1.6) - connection_pool (>= 3.0.0) - json (>= 2.16.0) - logger (>= 1.7.0) - rack (>= 3.2.0) - redis-client (>= 0.29.0) + sidekiq (8.0.10) + connection_pool (>= 2.5.0) + json (>= 2.9.0) + logger (>= 1.6.2) + rack (>= 3.1.0) + redis-client (>= 0.23.2) sidekiq-failures (1.1.0) sidekiq (>= 4.0.0) sidekiq-logstash (3.3.0) @@ -475,12 +492,18 @@ GEM rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - ssrf_filter (1.5.0) - stackprof (0.2.28) - standard (1.56.0) + sshkit (1.25.1) + base64 + logger + net-scp (>= 1.1.2) + net-sftp (>= 2.1.2) + net-ssh (>= 2.8.0) + ostruct + stackprof (0.2.22) + standard (1.54.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.88.0) + rubocop (~> 1.84.0) standard-custom (~> 1.0.0) standard-performance (~> 1.8) standard-custom (1.0.2) @@ -489,23 +512,27 @@ GEM standard-performance (1.9.0) lint_roller (~> 1.1) rubocop-performance (~> 1.26.0) - stimulus-rails (1.3.4) + stimulus-rails (1.1.1) railties (>= 6.0.0) - super_diff (0.19.0) - attr_extras (>= 6.2.4, < 8) - diff-lcs (~> 1.5) - patience_diff (~> 1.2) - tailwindcss-rails (4.6.0) + stringio (3.2.0) + super_diff (0.18.0) + attr_extras (>= 6.2.4) + diff-lcs + patience_diff + tailwindcss-rails (4.4.0) railties (>= 7.0.0) tailwindcss-ruby (~> 4.0) - tailwindcss-ruby (4.3.3-arm64-darwin) - tailwindcss-ruby (4.3.3-x86_64-linux-gnu) - temple (0.10.4) + tailwindcss-ruby (4.2.2-arm64-darwin) + tailwindcss-ruby (4.2.2-x86_64-linux-gnu) + temple (0.9.1) thor (1.5.0) - tilt (2.8.0) - timeout (0.6.1) + thread_safe (0.3.6) + thruster (0.1.23-arm64-darwin) + thruster (0.1.23-x86_64-linux) + tilt (2.0.11) + timeout (0.5.0) tsort (0.2.0) - turbo-rails (2.0.23) + turbo-rails (2.0.20) actionpack (>= 7.1.0) railties (>= 7.1.0) turbo_tests (2.2.5) @@ -519,26 +546,30 @@ GEM uri (1.1.1) useragent (0.16.11) vcr (6.4.0) - view_component (4.12.0) + view_component (4.5.0) actionview (>= 7.1.0) activesupport (>= 7.1.0) concurrent-ruby (~> 1) + virtus (2.0.0) + axiom-types (~> 0.1) + coercible (~> 1.0) + descendants_tracker (~> 0.0, >= 0.0.3) warden (1.2.9) rack (>= 2.0.9) - webmock (3.26.2) + webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) websocket (1.2.11) - websocket-driver (0.8.2) + websocket-driver (0.8.0) base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) yaml (0.4.0) - yard (0.9.45) - zeitwerk (2.8.2) + yard (0.9.39) + zeitwerk (2.7.5) PLATFORMS arm64-darwin-21 @@ -572,6 +603,7 @@ DEPENDENCIES honeybadger hotwire-livereload importmap-rails + kamal letter_opener listen lograge (>= 0.14) @@ -605,6 +637,7 @@ DEPENDENCIES stimulus-rails super_diff tailwindcss-rails + thruster turbo-rails turbo_tests tzinfo-data @@ -613,7 +646,7 @@ DEPENDENCIES webmock RUBY VERSION - ruby 4.0.6 + ruby 4.0.6p0 BUNDLED WITH - 4.0.17 + 2.7.2 diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100755 index 0000000..6b6f4de --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,17 @@ +#!/bin/bash -e + +# If running the rails server, provision the app's database user and run migrations +if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then + # Create the app's Postgres user and database if they don't exist + if [ -n "$POSTGRES_PASSWORD" ] && [ -n "$POSTGRESQL_ADDRESS" ]; then + echo "Ensuring database user and database exist..." + PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$POSTGRESQL_ADDRESS" -U postgres -tc \ + "SELECT 1 FROM pg_roles WHERE rolname='$POSTGRESQL_USERNAME'" | grep -q 1 || \ + PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$POSTGRESQL_ADDRESS" -U postgres -c \ + "CREATE ROLE $POSTGRESQL_USERNAME WITH LOGIN PASSWORD '$POSTGRES_PASSWORD' CREATEDB;" + fi + + ./bin/rails db:prepare +fi + +exec "${@}" diff --git a/bin/kamal b/bin/kamal new file mode 100755 index 0000000..d9ba276 --- /dev/null +++ b/bin/kamal @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kamal' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kamal", "kamal") diff --git a/bin/thrust b/bin/thrust new file mode 100755 index 0000000..b1c45d1 --- /dev/null +++ b/bin/thrust @@ -0,0 +1,16 @@ +#!/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__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("thruster", "thrust") diff --git a/config/database.yml b/config/database.yml index b154c71..0f76e0b 100644 --- a/config/database.yml +++ b/config/database.yml @@ -26,6 +26,7 @@ test: production: <<: *default - database: convus_reviews_production - username: convus_reviews - password: <%= ENV["CONVUS_REVIEWS_DATABASE_PASSWORD"] %> + host: <%= ENV.fetch("POSTGRESQL_ADDRESS", "localhost") %> + username: <%= ENV.fetch("POSTGRESQL_USERNAME", "convus_webapp") %> + password: <%= ENV["POSTGRES_PASSWORD"] %> + database: convus_webapp_production diff --git a/config/deploy.yml b/config/deploy.yml new file mode 100644 index 0000000..c0627f3 --- /dev/null +++ b/config/deploy.yml @@ -0,0 +1,89 @@ +<% server_ip = "SERVER_IP_PLACEHOLDER" %> +<% app_name = "convus_webapp" %> +<% app_host = "APP_HOST_PLACEHOLDER" %> + +# Name of your application. Used to uniquely configure containers. +service: <%= app_name %> + +# Name of the container image (use your-user/app-name on external registries). +image: convus/<%= app_name %> + +# Deploy to these servers. +servers: + web: + - <%= server_ip %> + worker: + hosts: + - <%= server_ip %> + cmd: bundle exec sidekiq + +proxy: + ssl: true + host: <%= app_host %> + healthcheck: + path: /up + +# Where you keep your container images. +registry: + server: ghcr.io + username: convus + password: + - KAMAL_REGISTRY_PASSWORD + +# Inject ENV variables into containers (secrets come from .kamal/secrets). +env: + secret: + - RAILS_MASTER_KEY + - SECRET_KEY_BASE + - HONEYBADGER_API_KEY + - POSTGRES_PASSWORD + - DEVISE_SECRET_KEY + - DEVISE_PEPPER + clear: + POSTGRESQL_ADDRESS: shared-db + POSTGRESQL_USERNAME: <%= app_name %> + REDIS_URL: redis://shared-redis:6379/0 + +# Aliases are triggered with "bin/kamal ". You can overwrite arguments on invocation: +# "bin/kamal logs -r job" will tail logs from the first server in the job section. +aliases: + console: app exec --interactive --reuse "bin/rails console" + shell: app exec --interactive --reuse "bash" + logs: app logs -f + dbc: app exec --interactive --reuse "bin/rails dbconsole --include-password" + +# Use a persistent storage volume for local Active Storage files. +# Recommended to change this to a mounted volume path that is backed up off server. +volumes: + - "<%= app_name %>_storage:/rails/storage" + +# Bridge fingerprinted assets, like JS and CSS, between versions to avoid +# hitting 404 on in-flight requests. Combines all files from new and old +# version inside the asset_path. +asset_path: /rails/public/assets + +# Configure the image builder. +builder: + arch: amd64 + # Build image via remote server (useful for faster amd64 builds on arm64 computers) + remote: ssh://root@<%= server_ip %> + +# Shared accessories — used by all apps on this server. +# Other apps can define the same accessories (Kamal will skip if already running). +# WARNING: do not run `kamal accessory stop/remove` from any app — it affects all apps. +accessories: + redis: + service: shared-redis + image: redis:7 + host: <%= server_ip %> + directories: + - shared_redis_data:/data + db: + service: shared-db + image: postgres:17 + host: <%= server_ip %> + env: + secret: + - POSTGRES_PASSWORD + directories: + - shared_db_data:/var/lib/postgresql/data