diff --git a/.env.example b/.env.example index cd535cd..5d1f926 100644 --- a/.env.example +++ b/.env.example @@ -11,8 +11,6 @@ APP_VERSION=dev # secrets KAMAL_REGISTRY_PASSWORD=redacted -RAILS_MASTER_KEY=redacted -POSTGRES_PASSWORD=redacted # pdf generation binary WKHTMLTOPDF_PATH=/usr/local/bin/wkhtmltopdf diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f0527e6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: bundler + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0197e1e..e5dbb70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: - name: Install Ruby and gems uses: ruby/setup-ruby@v1 with: - ruby-version: 3.3.5 + ruby-version: .ruby-version bundler-cache: true - name: Set up database schema run: bin/rails db:schema:load @@ -42,7 +42,7 @@ jobs: - name: Install Ruby and gems uses: ruby/setup-ruby@v1 with: - ruby-version: 3.3.5 + ruby-version: .ruby-version bundler-cache: true - name: Security audit dependencies run: bundle exec bundler-audit --update @@ -50,8 +50,10 @@ jobs: run: bundle exec bundle audit - name: Security audit application code run: bin/brakeman -q -w2 + - name: Scan for security vulnerabilities in JavaScript dependencies + run: bin/importmap audit - name: Lint Ruby files - run: bin/rubocop --parallel + run: bin/rubocop --parallel -f github - name: Install Hadolint run: | wget -O ./hadolint https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64 @@ -73,7 +75,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.3.5 + ruby-version: .ruby-version bundler-cache: true - name: Set up Docker Buildx id: buildx @@ -107,7 +109,6 @@ jobs: env: RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} - SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} APP_VERSION: ${{ github.ref_name }} @@ -122,7 +123,5 @@ jobs: - uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - - name: Env push command - run: bundle exec kamal env push - name: Deploy command run: bundle exec kamal deploy --skip-push --version ${{ github.ref_name }} diff --git a/.github/workflows/kamal.yml b/.github/workflows/kamal.yml index a7cdbbb..0fb3822 100644 --- a/.github/workflows/kamal.yml +++ b/.github/workflows/kamal.yml @@ -14,7 +14,6 @@ jobs: env: RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} - SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} steps: @@ -26,6 +25,6 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.3.5 + ruby-version: .ruby-version - name: Run KAMAL command run: ${{ github.event.inputs.command }} diff --git a/.kamal/secrets b/.kamal/secrets new file mode 100644 index 0000000..73f9f9d --- /dev/null +++ b/.kamal/secrets @@ -0,0 +1,19 @@ +# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, +# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either +# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. + +# Example of extracting secrets from 1password (or another compatible pw manager) +# SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) +# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS}) +# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS}) + +# Use a GITHUB_TOKEN if private repositories are needed for the image +# GITHUB_TOKEN=$(gh config get -h github.com oauth_token) + +# Grab the registry password from ENV +KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD +POSTGRES_PASSWORD=$POSTGRES_PASSWORD +APP_VERSION=$APP_VERSION + +# Improve security by using a password manager. Never check config/master.key into git! +RAILS_MASTER_KEY=$RAILS_MASTER_KEY diff --git a/Gemfile b/Gemfile index 061ab9b..02fd80f 100644 --- a/Gemfile +++ b/Gemfile @@ -76,7 +76,7 @@ group :development do gem 'brakeman', require: false gem 'bundle-audit', require: false gem 'htmlbeautifier' - gem 'kamal' + gem 'kamal', '~> 2.0' gem 'web-console' end diff --git a/Gemfile.lock b/Gemfile.lock index 35e17d2..af1ab8d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -149,10 +149,10 @@ GEM devise-i18n (1.12.1) devise (>= 4.9.0) docile (1.4.1) - dotenv (2.8.1) - dotenv-rails (2.8.1) - dotenv (= 2.8.1) - railties (>= 3.2) + dotenv (3.1.4) + dotenv-rails (3.1.4) + dotenv (= 3.1.4) + railties (>= 6.1) drb (2.2.1) dry-initializer (3.1.1) ed25519 (1.3.0) @@ -175,7 +175,7 @@ GEM globalid (1.2.1) activesupport (>= 6.1) htmlbeautifier (1.4.3) - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) @@ -185,7 +185,7 @@ GEM activesupport (>= 6.0.0) railties (>= 6.0.0) io-console (0.7.2) - irb (1.14.0) + irb (1.14.1) rdoc (>= 4.0.0) reline (>= 0.4.2) jbuilder (2.12.0) @@ -193,16 +193,16 @@ GEM activesupport (>= 5.0.0) jmespath (1.6.2) json (2.7.2) - kamal (1.8.1) + kamal (2.0.0) activesupport (>= 7.0) base64 (~> 0.2) bcrypt_pbkdf (~> 1.0) concurrent-ruby (~> 1.2) - dotenv (~> 2.8) + dotenv (~> 3.1) ed25519 (~> 1.2) net-ssh (~> 7.0) sshkit (>= 1.23.0, < 2.0) - thor (~> 1.2) + thor (~> 1.3) zeitwerk (~> 2.5) kaminari (1.2.2) activesupport (>= 4.1.0) @@ -220,7 +220,7 @@ GEM kaminari rails language_server-protocol (3.17.0.3) - logger (1.6.0) + logger (1.6.1) logtail (0.1.13) msgpack (~> 1.0) logtail-rack (0.2.5) @@ -244,7 +244,7 @@ GEM matrix (0.4.2) mini_magick (4.13.0) mini_mime (1.1.5) - minitest (5.24.1) + minitest (5.25.1) mission_control-jobs (0.3.1) importmap-rails irb (~> 1.13) @@ -280,6 +280,7 @@ GEM nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) + ostruct (0.6.0) parallel (1.26.3) parser (3.3.3.0) ast (~> 2.4.1) @@ -292,7 +293,7 @@ GEM psych (5.1.2) stringio public_suffix (5.0.5) - puma (6.4.2) + puma (6.4.3) nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) @@ -349,7 +350,7 @@ GEM redis-client (0.22.2) connection_pool regexp_parser (2.9.2) - reline (0.5.9) + reline (0.5.10) io-console (~> 0.5) responders (3.1.1) actionpack (>= 5.2) @@ -445,16 +446,17 @@ GEM sqlite3 (1.7.3-x86-linux) sqlite3 (1.7.3-x86_64-darwin) sqlite3 (1.7.3-x86_64-linux) - sshkit (1.23.0) + sshkit (1.23.1) base64 net-scp (>= 1.1.2) net-sftp (>= 2.1.2) net-ssh (>= 2.8.0) + ostruct stimulus-rails (1.3.3) railties (>= 6.0.0) stringio (3.1.1) strscan (3.1.0) - thor (1.3.1) + thor (1.3.2) tilt (2.3.0) timeout (0.4.1) turbo-rails (2.0.5) @@ -471,7 +473,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webrick (1.8.1) + webrick (1.8.2) websocket (1.2.10) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -495,7 +497,7 @@ GEM anyway_config (>= 1.3, < 3) railties yabeda (~> 0.8) - zeitwerk (2.6.17) + zeitwerk (2.6.18) PLATFORMS aarch64-linux @@ -523,7 +525,7 @@ DEPENDENCIES image_processing (~> 1.2) importmap-rails jbuilder - kamal + kamal (~> 2.0) kaminari kaminari-i18n logtail-rails (~> 0.2.7) diff --git a/config/application.rb b/config/application.rb index 002e25f..d9d8046 100644 --- a/config/application.rb +++ b/config/application.rb @@ -29,12 +29,5 @@ class Application < Rails::Application config.i18n.default_locale = :it config.hosts = [] - if Rails.env.production? - config.logger = Logtail::Logger.create_default_logger( - Rails.application.credentials.dig( - :vector, :rails_logs_token - ) - ) - end end end diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index d93baf7..87fe214 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -/K5V6jf8psjxWALA9phkzER3c/3aszNe6tfG8eCGSGFulMESSC6T3k6gQsGQvvQHzVv1u7KsNoGjd86ZW3shpWR5IaPPGF/HKqxhHgyZZF8dVrMRyOlhI/VaNCIWOIhXgq2uTZiw+4LWBaqKF+ecCpZxAdFZhBx8DMmXGfoPi2yXuF6zsL+zVnxsgsSTe1zhArFsSI119g1nGOa2DgKU1ScMh1o7viVtTOjtfb+zJ/g7RBHs3xXdOAR0Fy4g/JghwcwfeYbHH0PaWzN38bUFTijMpCAqoeD8+71iKIchTF8v6Q0Lg3X6cItOS57258WlvnpIzfW8N7mTZjiXz9MiqRTehW56aLzS1q5L+RqRIw3tNI0rc3NsDpLNIvT6l+SOFEwWhr/9tABWz+Z/+EcgPPl8PJZUwJuSar43kIHch0NEZIkq1ABa25t2W3qiOHJZJui5g0sGFhQA8F8cFfr+/hvjSvmMpnMQLnNZGeML7zpsKkaQZcAauaBqso1oltigY28ax7VCwd+a0lO2MiBXUde82LT4aOK0PavpSsaeM/KINIlfxL/+QL6AleyDYj5sFAKxtz9w1vXymGYwNZp8oC1BtVwQlelrsZq9Orlz/RhomF15J/pNrItkKz1mcvfwaqv4JsXTNxGiFDtNIo4d4aV7rbzdaqKLwZEpz8JwUzI+CbOX--CI1brbTk84ZXJ19+--KSyG1pYUx1cb6+LPcrsX6A== \ No newline at end of file +0jzAHU8ow9zF8PpcXF9uvHb1G4gMQnqaDND2rEdqpdKJYsWAVXDsvswNgIpHFgtk3Rrh18nLQhASM87NUXoflDuUB+Rie0v1UceAkJk+0Zus2z4r7+QYe/dnkFj6/8ytDk1aOe62P9ZuoX1arffDE5m2Okw3heGl71oD5S159gZCtsDK+uMzcKzF2Sj8cFpr7VsDyIDXYIA/EgNKEzFaryfdtFWEIcAzf3fUzJESmFdr/2wNPckWIHUMyO8FGkinh0kLcjql3YJ8nicnOP2G55545ddrJUhDo9vCFqmiuMrZjhqiDeuzyOjhZp6sK/DczmwUrvn4sF2Cmyl9olpx0sO2urlZJSyKsdNaEsRPhD+HJ2tA4XC551DKorr+uCFunHFZMeRauefKJFcnIKqMuFVPzdUW/Lar1JUncGsjWBLFmAz08BJmKnJEdatinlqdIA+uRQ75PEos3QMWbniF5HHaIyfLNPfnSc46AI2zmFsMCLKcPkBH2WJeTDpE50gqDT1G7CzwKbe3trPkhTXdQqffHLpX362YhZfW8cgfVbQS5PGFn8VKP9b4GyxIHUjKQc/ZzdlN0ZvH2Y+sz8D43QsiHwKKm3HYgOMepO7W2dQKeAc3be9hgbdYvdAJhUKpMcnnhzAif5z+i3fMEqSwOm/bdyqiWEIL60B+54x60J2QfqTdgo0x/mt27ZLx3vmGQbCEtK1/znA9SEVTUrbIpDZCBeQ9JQ5nHw==--GR9yMZHk6YurLTID--V1FXeATVLIMqP47eowGGbA== \ No newline at end of file diff --git a/config/database.yml b/config/database.yml index 40ac102..a4bacaf 100644 --- a/config/database.yml +++ b/config/database.yml @@ -20,5 +20,4 @@ test: production: primary: <<: *default - password: <%= ENV.fetch('POSTGRES_PASSWORD', 'opengas') %> - + password: <%= Rails.application.credentials.dig(:pg, :password) %> diff --git a/config/deploy.yml b/config/deploy.yml index b52da0d..080c1a6 100644 --- a/config/deploy.yml +++ b/config/deploy.yml @@ -9,14 +9,15 @@ servers: web: hosts: - opengas.eu - labels: - traefik.http.routers.open_gas.entrypoints: websecure - traefik.http.routers.open_gas.rule: Host(`opengas.eu`) - traefik.http.routers.open_gas.tls.certresolver: letsencrypt options: add-host: host.docker.internal:host-gateway - volume: - - data:/rails/file_store + +# Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server). +# Set ssl: false if using something like Cloudflare to terminate SSL (but keep host!). +proxy: + ssl: true + app_port: 3000 + host: opengas.eu # Credentials for your image host. registry: @@ -24,7 +25,7 @@ registry: # server: registry.digitalocean.com / ghcr.io / ... username: baldarn - # Always use an access token rather than real password when possible. + # Always use an access token ratherg than real password when possible. password: - KAMAL_REGISTRY_PASSWORD @@ -38,26 +39,61 @@ env: SMTP_PORT: 587 SMTP_ADDRESS: smtp.ionos.it SMTP_USERNAME: info@opengas.eu + # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. + # When you start using multiple servers, you should split out job processing to a dedicated machine. + SOLID_QUEUE_IN_PUMA: true + + # Set number of processes dedicated to Solid Queue (default: 1) + # JOB_CONCURRENCY: 3 + + # Set number of cores available to the application on each server (default: 1). + # WEB_CONCURRENCY: 2 + + # Log everything from Rails + # RAILS_LOG_LEVEL: debug + secret: - RAILS_MASTER_KEY - - POSTGRES_PASSWORD - APP_VERSION +# 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" + + +# Use a persistent storage volume for sqlite database files and local Active Storage files. +# Recommended to change this to a mounted volume path that is backed up off server. +volumes: + - "open_gas_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://docker@docker-builder-server + # + # # Pass arguments and secrets to the Docker build process + # args: + # RUBY_VERSION: ruby-3.3.5 + # secrets: + # - GITHUB_TOKEN + # - RAILS_MASTER_KEY + # Use a different ssh user than root # ssh: # user: app -# Configure builder setup. -# builder: -# args: -# RUBY_VERSION: 3.2.0 -# secrets: -# - GITHUB_TOKEN -# remote: -# arch: amd64 -# host: ssh://app@192.168.0.1 - -# Use accessory services (secrets come from .env). +# Use accessory services (secrets come from .kamal/secrets). accessories: db: image: postgres:16.4 @@ -79,55 +115,3 @@ accessories: port: 6379 directories: - data:/data - -# Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it. -traefik: - options: - publish: - - "443:443" - - "8080:8080" - volume: - - "/letsencrypt/acme.json:/letsencrypt/acme.json" - args: - api.dashboard: true - api.insecure: true - entryPoints.web.address: ":80" - entryPoints.websecure.address: ":443" - # entryPoints.web.http.redirections.entryPoint.to: websecure - # entryPoints.web.http.redirections.entryPoint.scheme: https - # entryPoints.web.http.redirections.entrypoint.permanent: true - certificatesResolvers.letsencrypt.acme.email: "lorenzo.farnararo@gmail.com" - certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json" - certificatesResolvers.letsencrypt.acme.httpchallenge: true - certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web - -# Configure a custom healthcheck (default is /up on port 3000) -# healthcheck: -# path: /healthz -# port: 4000 - -# 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. -# -# If your app is using the Sprockets gem, ensure it sets `config.assets.manifest`. -# See https://github.com/basecamp/kamal/issues/626 for details -# -asset_path: /rails/public/assets - -# Configure rolling deploys by setting a wait time between batches of restarts. -# boot: -# limit: 10 # Can also specify as a percentage of total hosts, such as "25%" -# wait: 2 - -# Configure the role used to determine the primary_host. This host takes -# deploy locks, runs health checks during the deploy, and follow logs, etc. -# -# Caution: there's no support for role renaming yet, so be careful to cleanup -# the previous role on the deployed hosts. -# primary_role: web - -# Controls if we abort when see a role with no hosts. Disabling this may be -# useful for more complex deploy configurations. -# -# allow_empty_roles: false diff --git a/config/environments/production.rb b/config/environments/production.rb index 2d939ae..60be458 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -98,6 +98,12 @@ enable_starttls: true } + config.logger = Logtail::Logger.create_default_logger( + Rails.application.credentials.dig( + :vector, :rails_logs_token + ) + ) + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true diff --git a/config/puma.rb b/config/puma.rb index d0890b2..d398337 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -3,13 +3,17 @@ # 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. - +# # Puma starts a configurable number of processes (workers) and each process # serves each request in a thread from an internal thread pool. # +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# # The ideal number of threads per worker depends both on how much time the # application spends waiting for IO operations and on how much you wish to -# to prioritize throughput over latency. +# prioritize throughput over latency. # # As a rule of thumb, increasing the number of threads will increase how much # traffic a given process can handle (throughput), but due to CRuby's @@ -31,10 +35,9 @@ # Allow puma to be restarted by `bin/rails restart` command. plugin :tmp_restart -plugin :solid_queue - -activate_control_app -plugin :yabeda +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV['SOLID_QUEUE_IN_PUMA'] -# Only use a pidfile when requested +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. pidfile ENV['PIDFILE'] if ENV['PIDFILE'] diff --git a/config/solid_queue.yml b/config/solid_queue.yml index f3c89cb..1649029 100644 --- a/config/solid_queue.yml +++ b/config/solid_queue.yml @@ -15,7 +15,7 @@ default: &default - urgent - default threads: 3 - processes: 1 + processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %> polling_interval: 1 development: