A collection of Rails generators from the Maquina umbrella. Each generator produces standalone code with no runtime gem dependency -- the gem is only needed at generation time.
Clave (Spanish: "code/key") generates a complete passwordless authentication system using email verification codes.
- Models:
Account,User,Session,EmailVerification,Current - Controllers: Sign-in/sign-up flows with email code verification
- Views: Minimal, responsive forms styled with Tailwind CSS
- Mailer: Verification code emails (HTML + text)
- Job: Cleanup job for expired sessions and verifications
- Locale files: English and Spanish translations
- Migrations: 4 migrations (accounts, users, sessions, email_verifications)
- Test helper:
sign_in_as(user)andsign_outfor integration tests
Add to your Gemfile:
gem "maquina-generators", group: :developmentRun the generator:
rails g maquina:claveThen:
rails db:migrate # Run migrationsrails g maquina:clave # Full install
rails g maquina:clave --skip-registration # Sign-in only (no sign-up)
rails g maquina:clave --skip-views # Skip view templatesAll generated code lives in your app -- edit it directly:
- Redirect after login: Edit
app/controllers/concerns/authentication.rb(after_authentication_url) - Session duration: Edit
authentication.rb(default: 30 days) - Code expiration: Edit controllers (default: 15 minutes)
- Cooldown between codes: Edit
EmailVerification::COOLDOWN_MINUTES(default: 15) - Colors/styling: Edit view templates (default: indigo)
- Email sender: Edit
app/mailers/verification_mailer.rb - Translations: Edit
config/locales/clave.*.yml
Registration generates a password-based authentication system with multi-tenant account support. It builds on top of the Rails 8 authentication generator, adding an Account model (tenant), user roles, and a registration flow.
- Runs Rails authentication generator first (
bin/rails generate authentication) - Account model:
Accountwithnamefield andhas_many :users - Updated User model: Adds
belongs_to :account,roleenum (admin/member),namefield - Updated Current model: Adds
accountdelegation through the user - Registration controller: Creates Account + User (admin role) together in a transaction
- Views: Tailwind-styled registration form and updated login form with indigo color scheme
- Locale files: English and Spanish translations
- Migrations:
create_accountsandadd_account_fields_to_users
rails g maquina:registrationThen:
bundle install # Install bcrypt
rails db:migrate # Run migrationsrails g maquina:registration # Full install
rails g maquina:registration --skip-views # Skip view templatesAll generated code lives in your app -- edit it directly:
- Account fields: Edit
app/models/account.rbto add more tenant fields - User roles: Edit
app/models/user.rbto customize the role enum - Registration flow: Edit
app/controllers/registrations_controller.rb - Colors/styling: Edit view templates (default: indigo)
- Translations: Edit
config/locales/registration.*.yml
Solid Errors installs the solid_errors gem with HTTP authentication and engine mounting.
- BackstageController: Inherits from
ActionController::Base(bypasses app's ApplicationController concerns) - Initializer: Credentials-first auth with ENV variable fallback, database connection config
- Route: Mounts
SolidErrors::Engineat<prefix>/errors - Mailer templates: Self-contained
error_occurredHTML and text templates, sosend_emailsworks without the dashboard's helpers - Admin navigation: Shared navigation bar with Overview, Errors, Jobs and Security tabs
- Admin overview:
BackstageDashboardControllerat the prefix root with cards for each installed dashboard and a@metricsslot for your own aggregate counts; answers 503 until backstage credentials are set - Custom layout: Tailwind-styled layout with admin navigation and toast flash messages
- Stimulus controllers:
clipboard_controller.jsandbacktrace_filter_controller.js - Custom views: Tailwind-styled views to override the gem defaults (included by default, use
--no-copy-viewsto skip)
rails g maquina:solid_errors --prefix /admin
rails g maquina:solid_errors --prefix /admin --no-copy-views # Skip custom viewsThe generator automatically runs bundle install. After running, execute bin/rails generate solid_errors:install (decline the initializer overwrite to keep your config), then bin/rails db:migrate.
rails g maquina:solid_errors --prefix /admin # Default (with custom views)
rails g maquina:solid_errors --prefix /admin --no-copy-views # Without custom views
rails g maquina:solid_errors --prefix /backstage \
--user-env-var ADMIN_USER --password-env-var ADMIN_PASSWORD # Custom env varsCredentials are resolved in order:
Rails.application.credentials.backstage.username/.passwordENV["SOLID_ERRORS_USER"]/ENV["SOLID_ERRORS_PASSWORD"](configurable)
Mission Control Jobs installs the mission_control-jobs gem with HTTP authentication and engine mounting.
- BackstageController: Inherits from
ActionController::Basewith maquina_components helpers (bypasses app's ApplicationController concerns) - Helper:
MissionControlHelperwithjob_status_badge_variantandnav_icon_for_section - Initializer: Sets base controller class, credentials-first auth with ENV variable fallback
- Route: Mounts
MissionControl::Jobs::Engineat<prefix>/jobs - Admin navigation: Shared navigation bar with Overview, Errors, Jobs and Security tabs
- Admin overview:
BackstageDashboardControllerat the prefix root with cards for each installed dashboard and a@metricsslot for your own aggregate counts; answers 503 until backstage credentials are set - Custom layout: Tailwind-styled layout with admin navigation, toast flash messages, application/server selection, and tab navigation
- Custom views: Tailwind-styled views for jobs, queues, workers, and recurring tasks (included by default, use
--no-copy-viewsto skip)
rails g maquina:mission_control_jobs --prefix /admin
rails g maquina:mission_control_jobs --prefix /admin --no-copy-views # Skip custom viewsThe generator automatically runs bundle install.
rails g maquina:mission_control_jobs --prefix /admin # Default (with custom views)
rails g maquina:mission_control_jobs --prefix /admin --no-copy-views # Without custom views
rails g maquina:mission_control_jobs --prefix /backstage \
--user-env-var ADMIN_USER --password-env-var ADMIN_PASSWORD # Custom env varsCredentials are resolved in order:
Rails.application.credentials.backstage.username/.passwordENV["MISSION_CONTROL_JOBS_USER"]/ENV["MISSION_CONTROL_JOBS_PASSWORD"](configurable)
Solid Queue installs the solid_queue gem as the Active Job backend with configuration and Procfile.dev integration.
- Config:
config/solid_queue.ymlwith default dispatcher/worker settings - Application config: Sets
config.active_job.queue_adapter = :solid_queue(skipped in test environment) - Procfile.dev: Appends
solid_queue: bin/rails solid_queue:start - Migrations: Runs
solid_queue:install:migrations
rails g maquina:solid_queueThe generator automatically runs bundle install and installs migrations.
rails g maquina:solid_queue # Default (sqlite3)
rails g maquina:solid_queue --database postgresql # PostgreSQLRack Attack installs the rack-attack gem with rules that ban vulnerability scanners and addresses that ignore the throttle.
- Initializer:
config/initializers/rack_attack.rbwith the bans, throttles, safelist, 403 responder and an[ATTACK]log line per refusal. Its knobs are constants onRack::Attack(SCANNER_*,GENERAL_*,FLOOD_*,LOGIN_*) andRack::Attack.banned?(ip)answers whether an address is banned now.
rails g maquina:rack_attack
rails g maquina:rack_attack --login-path /sign_in # Throttle a different sign-in pathThe generator automatically runs bundle install.
- Scanner ban (Fail2Ban): three scanner paths in 10 minutes bans the IP for 7 days. Scanner paths are PHP files (
*.php), WordPress paths (wp-admin,wp-login, etc.), sensitive files anywhere in the path (.env,.git,/etc/passwd, etc.), backup archives (.zip,.sql,.tar.gz,.bak, etc.) and scanner targets (phpmyadmin,cgi-bin, etc.)./rails/active_storageis exempt. - Flood ban (Allow2Ban): 600 requests in 5 minutes (twice the general throttle) bans the IP for 1 day.
- Throttles: 300 requests/5min per IP (general;
/assetsand/rails/active_storageexempt), 5POST /session/20s per IP - Safelists: Localhost (
127.0.0.1,::1) - Responses: 403 Forbidden for blocklisted and banned, 429 Too Many Requests for throttled
Bans live in Rails.cache, so they need a shared cache store (Solid Cache) in production. Customize rules in config/initializers/rack_attack.rb. To see the refusals, add maquina:security.
Security records every Rack::Attack refusal in its own database and summarises the last week on a backstage page next to Solid Errors and Mission Control Jobs.
- Rack::Attack rules: runs
maquina:rack_attackunlessconfig/initializers/rack_attack.rbalready definesRack::Attack.banned?(an older initializer without it is replaced) - Subscriber:
config/initializers/rack_attack_events.rbwrites aSecurity::AbuseEventper throttled, blocked or banned request; a failed write never turns a 403 into a 500 - Models:
Security::Record(connects_tothesecuritydatabase),Security::AbuseEvent(30-day retention),Security::AbuseReport(the page's figures) - Database:
db/security_schema.rband asecurity:entry in every multi-database environment ofconfig/database.yml(andconfig/database.yml.example) - Retention: a daily
purge_abuse_eventstask inconfig/recurring.yml - BackstageController, controller (
Backstage::SecurityController), route (<prefix>/security), layout and views: stats, throttled addresses, blocked addresses (banned now, expired or not yet banned), scanner paths, sign-in throttles, targeted hosts, refusals by day, recent refusals (views skipped with--no-copy-views) - Admin navigation: a Security tab, added to an existing
_admin_navigationpartial too - Admin overview: the shared dashboard at the prefix root, if Solid Errors or Mission Control Jobs have not installed it already
The last 7 days of refusals, top five of each, read from Security::AbuseEvent through Security::AbuseReport. It is a briefing, not a log viewer: no filters, no pagination. Times are UTC.
- Stats: refused requests, throttled, blocked or banned, and distinct addresses
- Throttled addresses: over the general or sign-in limit, busiest first, with the rules they hit
- Blocked addresses: every address a blocklist refused, marked Banned now (Rack::Attack's own answer at render time), Expired, or Not banned -- an address with a strike or two on the scanner rule, or a slow scanner that stays under three paths in ten minutes
- Most requested blocked paths: what the scanners were after. A banned address is refused on every path, its ordinary pages included, so only the paths
Rack::Attack.scanner_path?recognises are listed - Sign-in throttles: addresses refused on
POST <login-path>, kept apart so scanner noise never buries them - Targeted hosts: which hostnames the refusals were aimed at
- By day: throttled, blocked and banned per day, zeros kept so a spike reads against its neighbours
- Recent refusals: the last 20, newest first
rails g maquina:security --prefix /admin
bin/rails db:prepareThe generator automatically runs bundle install. The page needs maquina_components.
rails g maquina:security --prefix /admin # Default
rails g maquina:security --prefix /admin --login-path /sign_in # Different sign-in path
rails g maquina:security --prefix /admin --no-copy-views # Without views
rails g maquina:security --prefix /backstage \
--user-env-var ADMIN_USER --password-env-var ADMIN_PASSWORD # Custom env varsHTTP Basic Auth, with credentials resolved in order:
Rails.application.credentials.backstage.username/.passwordENV["SECURITY_USER"]/ENV["SECURITY_PASSWORD"](configurable)
The page answers 503 until one of them is set, so it is never left open.
Localhost is safelisted, so your own requests are never refused. Rack::Attack trusts X-Forwarded-For from 127.0.0.1, so send one to stand in for another address:
for path in /wp-login.php /.env /backup.zip /; do
curl -s -o /dev/null -w "%{http_code} $path\n" -H "X-Forwarded-For: 203.0.113.10" http://localhost:3000$path
done
# 403 /wp-login.php
# 403 /.env
# 403 /backup.zip <- third scanner path: banned for 7 days
# 403 / <- banned, so every path is refusedDevelopment's :memory_store keeps bans inside the server process, so they reset on restart and a bin/rails runner script cannot see them. Production needs a shared store (Solid Cache).
App is a meta-generator that sets up a complete Rails application in one command. Run it after rails new myapp --css tailwind.
- Adds development, runtime, and production gems (brakeman, standard, rails-i18n, maquina-components, aws-sdk-s3, etc.)
- Creates
Procfile.dev - Creates
.rubocop.yml,.standard.yml, appends to.gitignore - Creates
config/initializers/generators.rb - Configures development (letter_opener) and production (APPLICATION_HOST) environments
- Configures
field_error_procand Solid Queue inapplication.rb - Installs Action Text and Active Storage
- Sets up ActiveStorage JavaScript imports
- Adds turbo morphing,
yield :head, and simplifies<main>tag in layout - Optionally installs authentication (
maquina:claveormaquina:registration) - Invokes sub-generators:
maquina:mission_control_jobs,maquina:solid_errors - Runs external installers:
solid_queue:install,solid_errors:install,solid_cache:install,solid_cable:install,maquina_components:install, thenmaquina:security(which installs themaquina:rack_attackrules) - Restores custom layouts overwritten by gem installers
- Configures multi-database
database.yml(primary, queue, cache, cable, errors, security) - Creates a HomeController with root route
- Generates a README and
database.yml.example - Runs
db:prepare
rails g maquina:app
rails g maquina:app --prefix /backstage --port 3100
rails g maquina:app --auth registration--prefix(default:/admin) -- Base path prefix for backstage tools (Solid Errors, Mission Control Jobs, Security)--port(default:3000) -- Default port for the development server--auth(default:none) -- Authentication type:none,clave, orregistration
bin/rails credentials:edit # set backstage username/password
bin/devCreate a new folder under lib/generators/maquina/:
lib/generators/maquina/your_generator/
your_generator_generator.rb
USAGE
templates/
...
The generator class should be Maquina::Generators::YourGeneratorGenerator and it will be available as rails g maquina:your_generator.
bundle install
rake test
bundle exec standardrb # Lint
bundle exec standardrb --fix # Auto-fixMIT License. See LICENSE.txt.