├── CNAME ├── spec ├── spec_helper.rb └── options_list_markdownizer_spec.rb ├── stylesheets ├── print.css ├── print.scss ├── modules │ ├── _img.scss │ ├── _projects.scss │ ├── nav │ │ ├── _nav--paginated.scss │ │ └── _nav--v.scss │ ├── _beta-banner.scss │ ├── ie8.css │ ├── _shared.scss │ ├── _search.scss │ ├── _footer.scss │ └── _header.scss ├── application.scss ├── _layout.scss ├── _load.scss ├── _base.scss ├── _type.scss ├── screen.scss └── application.css.map ├── favicon.ico ├── favicon.icns ├── fonts ├── icomoon.eot ├── icomoon.ttf └── icomoon.woff ├── images ├── mfa_login.png ├── sponsors.png ├── new-api-key.png ├── api-key-created.png ├── api-keys-index.png ├── new-mfa-api-key.png ├── settings-api-key.png ├── changing_mfa_step1.png ├── changing_mfa_step2.png ├── enabling_mfa_step1.png ├── enabling_mfa_step2.png ├── enabling_mfa_step3.png ├── mfa-required-since.png ├── new-api-key-gem-scope.png ├── enabling_webauthn_nickname.png ├── trusted-publishing │ ├── profile-gem-list.png │ ├── gem-owner-sidebar-links.png │ ├── pending-trusted-publisher-form.png │ ├── rubygem-trusted-publisher-form.png │ ├── pending-trusted-publisher-create.png │ ├── pending-trusted-publishers-index.png │ ├── rubygem-trusted-publisher-create.png │ ├── rubygem-trusted-publishers-index.png │ └── pending-trusted-publisher-form-filled.png └── managing-owners-using-ui │ ├── owners-index.png │ ├── rubygem-page.png │ ├── confirm-password.png │ └── rubygem-resend-confirmation.png ├── _config.yml ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitignore ├── Gemfile ├── bin ├── setup └── jekyll ├── .gitpod.yml ├── using-mfa-in-command-line.md ├── index.md ├── removing-a-published-gem.md ├── ssl-certificate-update.md ├── command-reference.erb ├── MIT-LICENSE ├── .github └── workflows │ ├── ci.yml │ └── scorecard.yml ├── trusted-publishing ├── releasing-gems.md ├── pushing-a-new-gem.md └── adding-a-publisher.md ├── README.md ├── organizations.md ├── lib └── options_list_markdownizer.rb ├── mfa-requirement-opt-in.md ├── credits.md ├── setting-up-webauthn-mfa.md ├── using-webauthn-mfa-in-command-line.md ├── rdoc └── generator │ └── template │ └── jekdoc │ └── classpage.rhtml ├── organizations ├── roles-and-permissions.md ├── transferring-gems.md ├── managing-members.md └── getting-started.md ├── javascripts └── mobile-nav.js ├── using-otp-mfa-in-command-line.md ├── rubygems-org-api-v2.md ├── trusted-publishing.md ├── setting-up-otp-mfa.md ├── name-your-gem.md ├── _plugins └── alias_generator.rb ├── setting-up-multifactor-authentication.md ├── rubygems-org-rate-limits.md ├── what-is-a-gem.md ├── using-s3-source.md ├── Rakefile ├── managing-owners-using-ui.md ├── publishing.md ├── resources.md ├── releasing-rubygems.md ├── cve.md ├── faqs.md ├── run-your-own-gem-server.md ├── api-key-scopes.md ├── security.md ├── rubygems-basics.md └── gems-with-extensions.md /CNAME: -------------------------------------------------------------------------------- 1 | guides.rubygems.org 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | -------------------------------------------------------------------------------- /stylesheets/print.css: -------------------------------------------------------------------------------- 1 | #container ul.nav { 2 | display: none; } 3 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/favicon.ico -------------------------------------------------------------------------------- /stylesheets/print.scss: -------------------------------------------------------------------------------- 1 | #container ul.nav { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /favicon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/favicon.icns -------------------------------------------------------------------------------- /fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/fonts/icomoon.eot -------------------------------------------------------------------------------- /fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/fonts/icomoon.ttf -------------------------------------------------------------------------------- /fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/fonts/icomoon.woff -------------------------------------------------------------------------------- /images/mfa_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/mfa_login.png -------------------------------------------------------------------------------- /images/sponsors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/sponsors.png -------------------------------------------------------------------------------- /images/new-api-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/new-api-key.png -------------------------------------------------------------------------------- /images/api-key-created.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/api-key-created.png -------------------------------------------------------------------------------- /images/api-keys-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/api-keys-index.png -------------------------------------------------------------------------------- /images/new-mfa-api-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/new-mfa-api-key.png -------------------------------------------------------------------------------- /images/settings-api-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/settings-api-key.png -------------------------------------------------------------------------------- /images/changing_mfa_step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/changing_mfa_step1.png -------------------------------------------------------------------------------- /images/changing_mfa_step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/changing_mfa_step2.png -------------------------------------------------------------------------------- /images/enabling_mfa_step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/enabling_mfa_step1.png -------------------------------------------------------------------------------- /images/enabling_mfa_step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/enabling_mfa_step2.png -------------------------------------------------------------------------------- /images/enabling_mfa_step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/enabling_mfa_step3.png -------------------------------------------------------------------------------- /images/mfa-required-since.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/mfa-required-since.png -------------------------------------------------------------------------------- /images/new-api-key-gem-scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/new-api-key-gem-scope.png -------------------------------------------------------------------------------- /images/enabling_webauthn_nickname.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/enabling_webauthn_nickname.png -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude: 3 | - README.md 4 | - CNAME 5 | - vendor 6 | permalink: "pretty" 7 | safe: true 8 | markdown: kramdown 9 | -------------------------------------------------------------------------------- /images/trusted-publishing/profile-gem-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/trusted-publishing/profile-gem-list.png -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG RUBY_VERSION=3.4.4 2 | FROM ghcr.io/rails/devcontainer/images/ruby:$RUBY_VERSION 3 | 4 | ENV BINDING="0.0.0.0" 5 | -------------------------------------------------------------------------------- /images/managing-owners-using-ui/owners-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/managing-owners-using-ui/owners-index.png -------------------------------------------------------------------------------- /images/managing-owners-using-ui/rubygem-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/managing-owners-using-ui/rubygem-page.png -------------------------------------------------------------------------------- /images/managing-owners-using-ui/confirm-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/managing-owners-using-ui/confirm-password.png -------------------------------------------------------------------------------- /images/trusted-publishing/gem-owner-sidebar-links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/trusted-publishing/gem-owner-sidebar-links.png -------------------------------------------------------------------------------- /images/trusted-publishing/pending-trusted-publisher-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/trusted-publishing/pending-trusted-publisher-form.png -------------------------------------------------------------------------------- /images/trusted-publishing/rubygem-trusted-publisher-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/trusted-publishing/rubygem-trusted-publisher-form.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.swp 3 | /.rvmrc 4 | /.sass-cache 5 | /_site 6 | /html 7 | /tmp 8 | 9 | ## Environment normalization: 10 | /.bundle/ 11 | /vendor/bundle 12 | -------------------------------------------------------------------------------- /images/managing-owners-using-ui/rubygem-resend-confirmation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/managing-owners-using-ui/rubygem-resend-confirmation.png -------------------------------------------------------------------------------- /images/trusted-publishing/pending-trusted-publisher-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/trusted-publishing/pending-trusted-publisher-create.png -------------------------------------------------------------------------------- /images/trusted-publishing/pending-trusted-publishers-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/trusted-publishing/pending-trusted-publishers-index.png -------------------------------------------------------------------------------- /images/trusted-publishing/rubygem-trusted-publisher-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/trusted-publishing/rubygem-trusted-publisher-create.png -------------------------------------------------------------------------------- /images/trusted-publishing/rubygem-trusted-publishers-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/trusted-publishing/rubygem-trusted-publishers-index.png -------------------------------------------------------------------------------- /images/trusted-publishing/pending-trusted-publisher-form-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubygems/guides/HEAD/images/trusted-publishing/pending-trusted-publisher-form-filled.png -------------------------------------------------------------------------------- /stylesheets/modules/_img.scss: -------------------------------------------------------------------------------- 1 | .t-body .t-img { 2 | border-radius: 0%; 3 | width: 98%; 4 | height: 100%; 5 | margin: 1%; 6 | } 7 | 8 | .t-body .t-img--small { 9 | width: 70%; 10 | } 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "jekyll", "~> 4.2" 6 | gem "mdl", "~> 0.11.0" 7 | gem "rake", "~> 13.0" 8 | gem "rdoc", "~> 6.4" 9 | gem "webrick", "~> 1.8" 10 | gem "csv", "~> 3.3" 11 | gem "base64", "~> 0.3" 12 | gem "logger", "~> 1.7" 13 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "fileutils" 4 | 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | puts "=== Installing dependencies ===" 13 | system("bundle check") || system!("bundle install") 14 | end 15 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-ruby-3.1 2 | tasks: 3 | - init: | 4 | # Code fetch for generating command guide into /workspace/rubygems 5 | git clone --depth 1 -b 3.3 https://github.com/rubygems/rubygems.git ../rubygems 6 | bundle config set path vendor/bundle 7 | bundle install 8 | command: bundle exec jekyll serve 9 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name: RubyGems Guides", 3 | "dockerFile": "Dockerfile", 4 | "forwardPorts": [ 5 | 4000 6 | ], 7 | "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", 8 | "postCreateCommand": "bin/setup", 9 | "customizations": { 10 | "vscode": { 11 | "extensions": [ 12 | "Shopify.ruby-lsp" 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "load"; 2 | @import "base"; 3 | @import "layout"; 4 | @import "type"; 5 | 6 | @import "modules/footer"; 7 | @import "modules/header"; 8 | @import "modules/projects"; 9 | @import "modules/search"; 10 | @import "modules/shared"; 11 | @import "modules/img"; 12 | @import "modules/beta-banner"; 13 | 14 | @import "modules/nav/nav--paginated"; 15 | @import "modules/nav/nav--v"; 16 | -------------------------------------------------------------------------------- /stylesheets/modules/_projects.scss: -------------------------------------------------------------------------------- 1 | .project__name { 2 | font: { 3 | weight: 300; 4 | size: 30px; 5 | } 6 | &:not(.is-first) { 7 | margin-top: 30px; 8 | padding-top: 22px; 9 | display: block; 10 | border-top: 1px solid lighten($gray, 13%); 11 | } 12 | } 13 | 14 | .project__links { 15 | margin: { 16 | top: 16px; 17 | bottom: 25px; 18 | } 19 | } 20 | 21 | .project__link { 22 | font: { 23 | weight: 400; 24 | size: 15px; 25 | } 26 | text-transform: uppercase; 27 | &:not(:last-child) { 28 | &:after { 29 | @extend %bullet; 30 | margin-left: 15px; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /using-mfa-in-command-line.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Using multi-factor authentication in command line 4 | url: /using-mfa-in-command-line 5 | previous: /setting-up-otp-mfa 6 | next: /using-webauthn-mfa-in-command-line 7 | --- 8 | How to use multi-factor authentication with gem CLI. 9 | 10 | Multi-factor authentication (MFA) greatly increases the security of your account. 11 | RubyGems currently requires that owners of any gem with more than 180 million 12 | cumulative downloads must enable MFA. 13 | 14 | You may enable MFA using [WebAuthn](/using-webauthn-mfa-in-command-line) or using [one-time passwords (OTP)](/using-otp-mfa-in-command-line). 15 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Guides 4 | previous: /credits 5 | next: /rubygems-basics 6 | --- 7 | 8 | Learn how RubyGems works, and how to make your own. 9 | 10 | The RubyGems software allows you to easily 11 | download, install, and use ruby software packages on your system. The 12 | software package is called a "gem" which contains a packaged Ruby 13 | application or library. 14 | 15 | Gems can be used to extend or modify 16 | functionality in Ruby applications. Commonly they're used to distribute 17 | reusable functionality that is shared with other Rubyists for use in 18 | their applications and libraries. Some gems provide command line 19 | utilities to help automate tasks and speed up your work. 20 | -------------------------------------------------------------------------------- /bin/jekyll: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'jekyll' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("jekyll", "jekyll") 28 | -------------------------------------------------------------------------------- /removing-a-published-gem.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Removing a published gem 4 | url: /removing-a-published-gem 5 | previous: /managing-owners-using-ui 6 | next: /ssl-certificate-update 7 | --- 8 | 9 | Published a gem before it was ready for release? Published a gem with the wrong name? 10 | 11 | Here's how you can fix it. 12 | 13 | You can use the gem yank command to remove versions from RubyGems.org's index using the command: 14 | 15 | ```ruby 16 | gem yank GEM -v VERSION 17 | ``` 18 | 19 | Running gem yank will remove your gem from being available with gem install and the other gem commands. This also removes the gem file, as of April 20, 2015. 20 | 21 | Note: Our webhook and mirror system means that several hundred services get pinged when new gems are pushed, so it's prudent to immediately reset any passwords/sensitive data you accidentally pushed even if you yank a gem right away. 22 | -------------------------------------------------------------------------------- /ssl-certificate-update.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: SSL Certificate Update 4 | url: /ssl-certificate-update 5 | previous: /removing-a-published-gem 6 | next: /patterns 7 | --- 8 | 9 | # SSL Certificate Updates 10 | 11 | If you’ve seen the following SSL error when trying to pull updates from RubyGems: 12 | 13 | ```OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed``` 14 | 15 | This error happens when your computer is missing a file that it needs to verify that the server behind RubyGems.org is the correct one. 16 | 17 | Follow the steps outlined in [the RubyGems and Bundler OpenSSL/TLS guide](https://bundler.io/v2.0/guides/rubygems_tls_ssl_troubleshooting_guide.html#troubleshooting-certificate-errors) to troubleshoot the problem. 18 | 19 | If you're still encountering issues, you can open an 20 | [issue on GitHub](https://github.com/rubygems/rubygems). 21 | -------------------------------------------------------------------------------- /stylesheets/modules/nav/_nav--paginated.scss: -------------------------------------------------------------------------------- 1 | .paginated-nav-links { 2 | margin-top: 90px; 3 | padding-top: 50px; 4 | position: relative; 5 | border-top: 1px solid $gray; 6 | overflow: auto; 7 | } 8 | 9 | .paginated-nav-link--prev, .paginated-nav-link--next { 10 | color: darken($gray, 13%); 11 | @include transition-duration(.25s); 12 | @include transition-property(color); 13 | &:focus, &:hover, &.is-active { 14 | color: $black; 15 | } 16 | &:focus { 17 | outline: none; 18 | } 19 | span { 20 | position: relative; 21 | top: -1px; 22 | text-transform: uppercase; 23 | } 24 | } 25 | 26 | .paginated-nav-link--prev { 27 | float: left; 28 | span { 29 | margin-left: 5px; 30 | } 31 | } 32 | 33 | .paginated-nav-link--next { 34 | position: absolute; 35 | top: 50px; 36 | right: 0; 37 | &:before, span { 38 | float: right; 39 | } 40 | &:before { 41 | position: relative; 42 | top: 1px; 43 | } 44 | span { 45 | margin-right: 7px; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /command-reference.erb: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Command Reference 4 | url: /command-reference 5 | previous: /specification-reference 6 | next: /rubygems-org-api 7 | --- 8 | 9 | What each `gem` command does, and how to use it. 10 | 11 | This reference was automatically generated from RubyGems version <%= Gem.rubygems_version.version %>. 12 | 13 | <%- commands.each do |name, command| -%><%= "* [gem #{name}](#gem-#{name})\n" %><%- end -%> 14 | <%- commands.each do |name, command| -%> 15 | 16 | ## gem <%= name %> 17 | 18 | <%= htmlify(command.summary) %> 19 | 20 | ### Usage 21 | 22 | <%= command.usage %> [options] 23 | <%= options_list(command).rstrip + "\n" -%> 24 | <%- if command.arguments != "" -%> 25 | 26 | ### Arguments 27 | 28 | <%- command.arguments.split("\n").each do |argument| -%><%= argument_list_item(argument) %> 29 | <%- end -%> 30 | <%- end -%> 31 | <%- if command.description && command.description != "" -%> 32 | 33 | ### Description 34 | 35 | <%= htmlify(command.description).gsub(/^ /, " ") %> 36 | <%- end -%> 37 | <%- end -%> 38 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Nick Quaranto 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /stylesheets/modules/nav/_nav--v.scss: -------------------------------------------------------------------------------- 1 | .nav--v__link, .nav--v__link--footer { 2 | display: block; 3 | font: { 4 | weight: 800; 5 | size: 12px; 6 | } 7 | line-height: 1.66; 8 | text-transform: uppercase; 9 | @include transition-duration(.25s); 10 | @include transition-property(color); 11 | &:not(:last-child) { 12 | margin-bottom: 10px; 13 | } 14 | &.is-active { 15 | color: $red; 16 | } 17 | } 18 | 19 | ul.nav--v { 20 | li { 21 | padding-bottom: 0.35em; 22 | } 23 | 24 | ul.nav--v { 25 | li { 26 | padding-bottom: 0.1em; 27 | } 28 | padding-left: 8%; 29 | } 30 | } 31 | 32 | .nav--v__link { 33 | padding-right: 32px; 34 | color: $black; 35 | @media (max-width: 779px) { 36 | &:last-child { 37 | margin-bottom: 60px; 38 | } 39 | } 40 | &:focus, &:hover, &:active { 41 | color: $red; 42 | } 43 | &:focus { 44 | outline: none; 45 | } 46 | } 47 | 48 | .nav--v__link--footer { 49 | color: $white; 50 | &:focus, &:hover, &:active { 51 | color: rgba($white, .3); 52 | } 53 | &:focus { 54 | outline: none; 55 | } 56 | @media (max-width: 779px) { 57 | &:last-child { 58 | margin-bottom: 36px; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /stylesheets/modules/_beta-banner.scss: -------------------------------------------------------------------------------- 1 | // Beta banner styles for limited beta features 2 | .beta-banner { 3 | background-color: #FFF3CD; 4 | border: 1px solid #FFE69C; 5 | border-radius: 8px; 6 | color: #856404; 7 | font-size: 14px; 8 | line-height: 1.5; 9 | margin-bottom: 1.5em; 10 | padding: 1em 1.25em; 11 | 12 | &__icon { 13 | display: inline-block; 14 | font-weight: bold; 15 | margin-right: 0.5em; 16 | } 17 | 18 | &__title { 19 | display: inline; 20 | font-weight: bold; 21 | margin-right: 0.5em; 22 | } 23 | 24 | &__text { 25 | display: inline; 26 | } 27 | 28 | a { 29 | color: #856404; 30 | text-decoration: underline; 31 | 32 | &:hover { 33 | color: #533f03; 34 | } 35 | } 36 | 37 | // Ensure good contrast and readability 38 | strong { 39 | font-weight: bold; 40 | color: #533f03; 41 | } 42 | } 43 | 44 | // Alternative info style beta banner 45 | .beta-banner--info { 46 | background-color: #D1ECF1; 47 | border-color: #BEE5EB; 48 | color: #0C5460; 49 | 50 | a { 51 | color: #0C5460; 52 | 53 | &:hover { 54 | color: #062E34; 55 | } 56 | } 57 | 58 | strong { 59 | color: #062E34; 60 | } 61 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ci 3 | 4 | on: [pull_request] 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | ci: 11 | runs-on: ubuntu-24.04 12 | steps: 13 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 14 | with: 15 | path: guides 16 | persist-credentials: false 17 | 18 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 19 | with: 20 | repository: ruby/rubygems 21 | path: rubygems 22 | ref: "4.0" 23 | persist-credentials: false 24 | 25 | - name: Set up Ruby 26 | uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 27 | with: 28 | ruby-version: 3.4.7 29 | rubygems: 4.0.0 30 | bundler-cache: true 31 | working-directory: guides 32 | 33 | - name: Build jekyll 34 | run: bundle exec jekyll build 35 | working-directory: guides 36 | 37 | - name: Generate command guide 38 | run: RUBYOPT="--disable-gems -I../rubygems/lib" RUBYGEMS_DIR="../rubygems" bundle exec rake command_guide && git diff --exit-code 39 | working-directory: guides 40 | 41 | - name: Lint generated markdown 42 | run: bundle exec mdl --rules MD009,MD012 command-reference.md 43 | working-directory: guides 44 | -------------------------------------------------------------------------------- /stylesheets/_layout.scss: -------------------------------------------------------------------------------- 1 | %l-wrap { 2 | margin: { 3 | right: auto; 4 | left: auto; 5 | } 6 | width: 90%; 7 | } 8 | 9 | .l-overflow { 10 | overflow: auto; 11 | } 12 | 13 | .l-wrap--b { 14 | @extend %l-wrap; 15 | max-width: 940px; 16 | } 17 | 18 | .l-colspan--l, .l-colspan--r { 19 | @extend %box-sizing; 20 | @media (min-width: 780px) { 21 | width: 75%; 22 | width: calc(100% - 240px); 23 | } 24 | } 25 | 26 | .l-colspan--l { 27 | @media (min-width: 780px) { 28 | padding-right: 60px; 29 | float: left; 30 | } 31 | } 32 | 33 | .l-colspan--r { 34 | @media (min-width: 780px) { 35 | float: right; 36 | } 37 | @media (min-width: 1200px) { 38 | padding-left: 60px; 39 | } 40 | } 41 | 42 | .l-col--l, .l-col--r { 43 | @extend %box-sizing; 44 | @media (min-width: 780px) { 45 | width: 25%; 46 | width: calc(240px); 47 | } 48 | } 49 | 50 | .l-col--l { 51 | @media (max-width: 779px) { 52 | margin-bottom: 60px; 53 | } 54 | @media (min-width: 780px) { 55 | float: left; 56 | } 57 | } 58 | 59 | .l-col--l--pad { 60 | @extend .l-col--l; 61 | @media (min-width: 780px) { 62 | padding-right: 60px; 63 | } 64 | } 65 | 66 | .l-col--r { 67 | @media (min-width: 780px) { 68 | float: right; 69 | } 70 | } 71 | 72 | .l-col--r--pad { 73 | @extend .l-col--r; 74 | @media (min-width: 780px) { 75 | padding-left: 60px; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /stylesheets/modules/ie8.css: -------------------------------------------------------------------------------- 1 | // Layout 2 | 3 | .l-col--l { 4 | margin-bottom: 60px; 5 | } 6 | 7 | 8 | 9 | // Shared 10 | 11 | .main--interior { 12 | padding-bottom: 86px; 13 | } 14 | 15 | .page__heading { 16 | padding-top: 50px; 17 | padding-bottom: 16px; 18 | font-size: 48px; 19 | } 20 | 21 | 22 | 23 | // Header 24 | 25 | .l-wrap--header { 26 | margin-right: auto; 27 | margin-left: auto; 28 | position: relative; 29 | max-width: 1100px; 30 | } 31 | 32 | .header { 33 | height: 68px; 34 | } 35 | 36 | .header__logo-wrap { 37 | top: 12px; 38 | font-size: 40px; 39 | } 40 | 41 | .header__nav-links-wrap { 42 | right: 0; 43 | } 44 | 45 | 46 | 47 | // Footer 48 | 49 | .l-wrap--footer { 50 | padding-right: 5% !important; 51 | padding-left: 5% !important; 52 | } 53 | 54 | .footer__about { 55 | margin-top: 60px !important; 56 | } 57 | 58 | .footer__sponsors-wrap { 59 | margin-right: auto; 60 | margin-left: auto; 61 | padding-right: 5%; 62 | padding-left: 5%; 63 | max-width: 940px; 64 | } 65 | 66 | .footer__sponsor { 67 | margin-right: 30px; 68 | width: 100px; 69 | } 70 | 71 | 72 | 73 | // Modals 74 | 75 | body.has-modal { 76 | overflow: hidden; 77 | } 78 | 79 | .modal-wrap { 80 | &.is-showing { 81 | position: absolute; 82 | } 83 | } 84 | 85 | .modal--sign-in, .modal--sign-up { 86 | margin-left: 5%; 87 | position: absolute; 88 | top: -150%; 89 | .modal-wrap.is-showing & { 90 | top: 60px; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /trusted-publishing/releasing-gems.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Releasing gems with a trusted publisher 4 | url: /trusted-publishing/releasing-gems 5 | previous: /trusted-publishing/pushing-a-new-gem 6 | next: /organizations 7 | --- 8 | 9 | Once you have a trusted publisher configured, you can use RubyGems' [`release-gem`](https://github.com/rubygems/release-gem) GitHub Action to set up your workflow to push gems to RubyGems.org. 10 | 11 | This looks almost exactly the same as normal, except that you don't need any explicit usernames, passwords, or API tokens: GitHub's OIDC identity provider will take care of everything for you: 12 | 13 | ```yaml 14 | jobs: 15 | push: 16 | runs-on: ubuntu-latest 17 | 18 | permissions: 19 | contents: write 20 | id-token: write 21 | 22 | # If you configured a GitHub environment on RubyGems, you must use it here. 23 | environment: release 24 | 25 | steps: 26 | # Set up 27 | - uses: actions/checkout@v5 28 | with: 29 | persist-credentials: false 30 | - name: Set up Ruby 31 | uses: ruby/setup-ruby@v1 32 | with: 33 | bundler-cache: true 34 | ruby-version: ruby 35 | 36 | # Release 37 | - uses: rubygems/release-gem@v1 38 | ``` 39 | 40 | Note the `id-token: write` permission: you **must** provide this permission at either the job level (strongly recommended) or workflow level (discouraged). Without it, the publishing action won't have sufficient permissions to identify itself to RubyGems.org. 41 | 42 | For more about `environment` setting, see: [Using Environment for your deployment (GitHub.com)](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-environments-for-deployment) 43 | -------------------------------------------------------------------------------- /stylesheets/modules/_shared.scss: -------------------------------------------------------------------------------- 1 | main, .header, .footer { 2 | @media (max-width: 1019px) { 3 | position: relative; 4 | left: 0; 5 | overflow: hidden; 6 | @include transition-duration(.25s); 7 | @include transition-property(left); 8 | &.mobile-nav-is-expanded { 9 | left: -270px; 10 | } 11 | } 12 | } 13 | 14 | main, .header { 15 | @media (max-width: 1019px) { 16 | &.mobile-nav-is-expanded { 17 | overflow: visible; 18 | } 19 | } 20 | } 21 | 22 | main { 23 | display: block; // For IE 11 24 | } 25 | 26 | .footer { 27 | @media (max-width: 1019px) { 28 | &.mobile-nav-is-expanded { 29 | overflow: hidden; 30 | } 31 | } 32 | } 33 | 34 | .main--interior { 35 | background-color: $white; 36 | @media (max-width: 929px) { 37 | padding-bottom: 86px; 38 | } 39 | @media (min-width: 930px) { 40 | padding-bottom: 175px; 41 | } 42 | } 43 | 44 | .page__heading { 45 | margin-bottom: 30px; 46 | border-bottom: 1px solid $gray; 47 | @media (max-width: 929px) { 48 | padding: { 49 | top: 18px; 50 | bottom: 10px; 51 | } 52 | } 53 | @media (min-width: 930px) { 54 | padding: { 55 | top: 30px; 56 | bottom: 30px; 57 | } 58 | } 59 | } 60 | 61 | a.page__heading { 62 | display: block; 63 | color: $black; 64 | @include transition-duration(.25s); 65 | @include transition-property(color); 66 | &:focus, &:hover, &:active { 67 | color: $red; 68 | } 69 | &:focus { 70 | outline: none; 71 | } 72 | } 73 | 74 | .colspan--l--has-border { 75 | @media (min-width: 780px) { 76 | border-right: 1px solid $gray; 77 | .footer & { 78 | border-right-color: rgba($white, .1); 79 | } 80 | } 81 | } 82 | 83 | .push { 84 | margin-top: 60px; 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RubyGems Guides 2 | =============== 3 | 4 | An effort to provide awesome documentation for the RubyGems ecosystem. 5 | 6 | Goals 7 | ----- 8 | 9 | * Be the definitive place for RubyGems knowledge 10 | * Help out those new to RubyGems get started and get things done 11 | * Make it easy to contribute more guides 12 | 13 | Want to help? 14 | ------------- 15 | 16 | If a guide is empty, start filling it out! Or, make a new one! Pull requests 17 | are gladly accepted! 18 | 19 | * Pick one from [the repo issues](https://github.com/rubygems/guides/issues) 20 | * Port content from help.rubygems.org knowledge base 21 | * Find lots of StackOverflow/ruby-talk questions and get their common answers in here 22 | * Fill out more guides! 23 | 24 | Setup 25 | ----- 26 | 27 | First, run `bundle install` and `bundle exec rake`. This will launch both Jekyll and 28 | Sass in "watch" mode. 29 | 30 | The pages will be available at http://localhost:4000/ 31 | 32 | Every guide except for the Command and Specification Reference is just a 33 | straight up markdown page, so just go edit it! 34 | 35 | For the Command Guide (`command-reference.md`), edit `command-reference.erb` 36 | and run: 37 | 38 | $ bundle exec rake command_guide 39 | 40 | For the Specification Guide, the documentation comes directly from the 41 | `Gem::Specification` class in RubyGems. Edit it, set your `RUBYGEMS_DIR` to 42 | where your code directory is, and run: 43 | 44 | $ RUBYGEMS_DIR=~/Dev/ruby/rubygems bundle exec rake spec_guide --trace 45 | 46 | Thanks 47 | ------ 48 | 49 | Huge thanks to [thoughtbot](https://thoughtbot.com) whose [playbook](https://thoughtbot.com/playbook) this is based off of. 50 | 51 | Legal 52 | ----- 53 | 54 | The actual content of the articles is licensed under Creative Commons. The code that this project consists of is licensed under MIT. 55 | -------------------------------------------------------------------------------- /organizations.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Organizations 4 | url: /organizations 5 | previous: /trusted-publishing/releasing-gems 6 | next: /organizations/getting-started 7 | --- 8 | 9 | # Organizations 10 | 11 |
16 | 17 | Organizations help teams and businesses collaborate, share gem ownership, manage permissions, and work together under a unified identity. 18 | 19 | ## What are Organizations? 20 | 21 | Organizations provide a shared space for multiple users to collectively manage RubyGems. Instead of individual ownership, gems belong to the organization, making it easier to: 22 | 23 | - **Maintain continuity** when team members change 24 | - **Control access** with role-based permissions 25 | - **Collaborate effectively** on gem development 26 | - **Establish identity** for your company or project 27 | 28 | ## Getting Started 29 | 30 | Ready to create your organization? Follow our [Getting Started Guide](/organizations/getting-started) to set up your first organization in minutes. 31 | 32 | ### Quick Links 33 | 34 | - [Create an Organization](/organizations/getting-started) 35 | - [Understanding Roles](/organizations/roles-and-permissions) 36 | - [Managing Members](/organizations/managing-members) 37 | - [Transferring Gems](/organizations/transferring-gems) 38 | 39 | ## Need Help? 40 | 41 | - Contact [support@rubygems.org](mailto:support@rubygems.org) for assistance 42 | - Report issues on [GitHub](https://github.com/rubygems/rubygems.org/issues) 43 | -------------------------------------------------------------------------------- /lib/options_list_markdownizer.rb: -------------------------------------------------------------------------------- 1 | class OptionsListMarkdownizer 2 | # Builds markdownized option list of a Gem::Command 3 | def call(command) 4 | ui = Gem::SilentUI.new 5 | Gem::DefaultUserInteraction.use_ui ui do 6 | # Invoke the Ruby options parser by asking for help. Otherwise the 7 | # options list in the parser will never be initialized. 8 | command.show_help 9 | end 10 | 11 | parser = command.send(:parser) 12 | options = '' 13 | helplines = parser.summarize 14 | helplines.each do |helpline| 15 | break if (helpline =~ /Arguments/) || (helpline =~ /Summary/) 16 | next if helpline.strip == '' 17 | 18 | helpline = markdownize_options helpline 19 | 20 | if helpline =~ /^\s{10,}(.*)/ 21 | options = options[0..-2] + " #{$1}\n" 22 | else 23 | if helpline =~ /^(.+)\s{2,}(.*)/ 24 | helpline = "#{$1} - #{$2}" 25 | end 26 | if helpline =~ /options/i 27 | options += "\n### #{helpline.strip.delete_suffix(":")}\n\n" 28 | else 29 | options += "* #{helpline.strip}\n" 30 | end 31 | end 32 | end 33 | options 34 | end 35 | 36 | # Marks options mentioned on the given summary line 37 | def markdownize_options(line) 38 | # Mark options up as code (also prevents change of -- to –) 39 | option_line_re = /^(\s*)((?:--[^\s]+|-[^\s])(?:, (?:--[^\s]+|-[^\s]))*(?: [A-Za-z_\[\],]+)*)(\s{3,})(.+)/ 40 | if option_line_re =~ line 41 | line.sub(option_line_re) do |m| 42 | "#{$1}`#{$2}`#{$3}" + 43 | markdownize_inline_options($4) 44 | end 45 | else 46 | markdownize_inline_options(line) 47 | end 48 | end 49 | 50 | private 51 | 52 | def markdownize_inline_options(line) 53 | line.gsub(/(?<=[\s\/])(--[\w\[\]\-]+|-\w)/, '`\1`') 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /mfa-requirement-opt-in.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: MFA requirement opt-in 4 | url: /mfa-requirement-opt-in 5 | previous: /using-otp-mfa-in-command-line 6 | next: /using-s3-source 7 | --- 8 | How to opt-in for MFA requirement. 9 | 10 | You can make your gems more secure by requiring all owners to enable MFA on their account. 11 | 12 | ## Opt-in to MFA requirement 13 | 14 | You can opt-in a gem you are managing by releasing a version that has 15 | `metadata.rubygems_mfa_required` set to `true`. 16 | 17 | % cat hola.gemspec 18 | Gem::Specification.new do |s| 19 | ... 20 | s.metadata = { "rubygems_mfa_required" => "true" } 21 | ... 22 | end 23 | 24 | The version being released with `rubygems_mfa_required` set and all the following versions 25 | will require you to have MFA enabled. 26 | Once enabled, the gem page will show `NEW VERSIONS REQUIRE MFA` in the sidebar, and all versions published with `rubygems_mfa_required` set will also show `VERSION PUBLISHED WITH MFA`: 27 | {:class="t-img t-img--small"} 28 | 29 | You will see the following error message if you have not enabled MFA and you are trying to release 30 | a new version for a gem that requires MFA: 31 | 32 | $ gem push hola-1.0.0.gem 33 | Pushing gem to https://rubygems.org... 34 | Rubygem requires owners to enable MFA. You must enable MFA before pushing new version. 35 | 36 | ## Disabling MFA requirement 37 | 38 | You can disable the MFA requirement by setting `rubygems_mfa_required` to `"false"` or any [`ActiveRecord::Type::Boolean::FALSE_VALUES`](https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html). 39 | 40 | **Note:** We will enforce the MFA requirement on the version being published. MFA requirement will be disabled after you have successfully 41 | published a gem with rubygems_mfa_required set to false. 42 | -------------------------------------------------------------------------------- /stylesheets/_load.scss: -------------------------------------------------------------------------------- 1 | $red: #e9573f; 2 | $blue: #53a4e0; 3 | $green: #4fcc9c; 4 | $yellow: #ffde00; 5 | $white: #ffffff; 6 | $gray: #c1c4ca; 7 | $d-gray: #42484d; 8 | $black: #141c22; 9 | 10 | %bullet { 11 | content: ""; 12 | margin-right: 15px; 13 | position: relative; 14 | top: -2px; 15 | display: inline-block; 16 | height: 7px; 17 | width: 7px; 18 | border-radius: 4px; 19 | background-color: $gray; 20 | } 21 | 22 | %border { 23 | margin-bottom: 48px; 24 | padding-bottom: 48px; 25 | border-bottom: { 26 | style: solid; 27 | color: $gray; 28 | } 29 | } 30 | 31 | %font-smoothing { 32 | -webkit-font-smoothing: antialiased; 33 | -moz-osx-font-smoothing: grayscale; 34 | } 35 | 36 | %box-sizing { 37 | -webkit-box-sizing: border-box; 38 | -moz-box-sizing: border-box; 39 | box-sizing: border-box; 40 | } 41 | 42 | @mixin calc($property, $expression, $fallback) { 43 | #{$property}: $fallback; 44 | #{$property}: -webkit-calc(#{$expression}); 45 | #{$property}: -moz-calc(#{$expression}); 46 | #{$property}: calc(#{$expression}); 47 | } 48 | 49 | @mixin transition-duration($transition-duration) { 50 | -webkit-transition-duration: $transition-duration; 51 | -moz-transition-duration: $transition-duration; 52 | -ms-transition-duration: $transition-duration; 53 | -o-transition-duration: $transition-duration; 54 | transition-duration: $transition-duration; 55 | } 56 | 57 | @mixin transition-property($transition-property...) { 58 | -webkit-transition-property: $transition-property; 59 | -moz-transition-property: $transition-property; 60 | -ms-transition-property: $transition-property; 61 | -o-transition-property: $transition-property; 62 | transition-property: $transition-property; 63 | } 64 | 65 | @mixin transition-delay($transition-delay) { 66 | -webkit-transition-delay: $transition-delay; 67 | -moz-transition-delay: $transition-delay; 68 | -o-transition-delay: $transition-delay; 69 | transition-delay: $transition-delay; 70 | } 71 | 72 | @mixin rotate($deg) { 73 | -webkit-transform: rotateY($deg); 74 | -ms-transform: rotateY($deg); 75 | transform: rotateY($deg); 76 | } 77 | -------------------------------------------------------------------------------- /credits.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Credits 4 | previous: /organizations/transferring-gems 5 | next: / 6 | --- 7 | 8 | This site is [open source](https://github.com/rubygems/guides) and its content is 9 | [Creative Commons](https://github.com/rubygems/guides/blob/gh-pages/CC-LICENSE) 10 | licensed. 11 | 12 | Contributors 13 | ------------ 14 | 15 | These people have donated time to creating and improving the RubyGems Guides site: 16 | 17 | * [Gabe Berke-Williams](https://github.com/gabebw) 18 | * [Gregory Brown](https://github.com/sandal) 19 | * [Amaia Castro](https://github.com/amaia) 20 | * [Ryan Davis](https://github.com/zenspider) 21 | * [Vijay Dev](https://github.com/vijaydev) 22 | * [Evgene Dzhelyov](https://github.com/edzhelyov) 23 | * [Mike Gunderloy](https://github.com/ffmike) 24 | * [Gabriel Horner](https://github.com/cldwalker) 25 | * [Richard Michael](https://github.com/richardkmichael) 26 | * [John Lees-Miller](https://github.com/jdleesmiller) 27 | * [Mark McSpadden](https://github.com/markmcspadden) 28 | * [Erik Michaels-Ober](https://github.com/sferik) 29 | * [Scott Moak](https://github.com/smoak) 30 | * [Jason Morrison](https://github.com/jasonm) 31 | * [Ryan Neufeld](https://github.com/rkneufeld) 32 | * [Nick Quaranto](https://github.com/qrush) 33 | * [Sebastian Spier](https://github.com/spier) 34 | * [Antonio Terceiro](https://github.com/terceiro) 35 | * [thrackle](https://github.com/thrackle) 36 | 37 | Acknowledgments 38 | --------------- 39 | 40 | Material for the Guides was adapted from these sources: 41 | 42 | * [Gem Packaging: Best Practices](https://weblog.rubyonrails.org/2009/9/1/gem-packaging-best-practices) 43 | * [Gem Sawyer, Modern Day Ruby Warrior](http://rubylearning.com/blog/2010/10/06/gem-sawyer-modern-day-ruby-warrior/) 44 | * [How to Name Gems](http://blog.segment7.net/2010/11/15/how-to-name-gems) 45 | * [Rubygems Good Practice](https://yehudakatz.com/2009/07/24/rubygems-good-practice/) 46 | * [Writing Ruby C extensions: Part 1](http://tenderlovemaking.com/2009/12/18/writing-ruby-c-extensions-part-1) 47 | * [Writing Ruby C extensions: Part 2](http://tenderlovemaking.com/2010/12/11/writing-ruby-c-extensions-part-2) 48 | 49 | Hosting 50 | ------- 51 | 52 | Hosted by [GitHub Pages](https://pages.github.com/). 53 | -------------------------------------------------------------------------------- /setting-up-webauthn-mfa.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Setting up WebAuthn / Passkey multi-factor authentication 4 | url: /setting-up-webauthn-mfa 5 | previous: /setting-up-multifactor-authentication 6 | next: /setting-up-otp-mfa 7 | --- 8 | 9 | Setting up WebAuthn multi-factor authentication 10 | 11 | Using WebAuthn for multi-factor authentication (MFA) is the best way to 12 | protect your account from takeover. It's stronger and easier to use than 13 | [OTP codes](/setting-up-otp-mfa). 14 | 15 | ## Prerequisite 16 | 17 | To use WebAuthn, you will need at least _one_ of the following: 18 | 19 | * A hardware security token (sometimes called a security key), such as a 20 | YubiKey or Google Titan Key. 21 | * A built-in hardware device, such as TouchID, FaceID or Windows Hello. 22 | * A browser that supports the "Passkey" standard. Up-to-date versions of 23 | Chrome, Safari, Firefox and Edge all support this standard. 24 | 25 | Unfortunately implementations of these experiences vary, so we can't show 26 | the exact details, but we will point out the steps that are specific to 27 | using RubyGems.org. 28 | 29 | ## Enabling WebAuthn multi-factor authentication 30 | 31 | 1. Login to rubygems.org using your existing account and go to the 32 | [edit settings](https://rubygems.org/settings/edit) page. 33 | 2. In the "Multi-factor Authentication" section you will see two options: 34 | "Authentication App" and "Security Device". Under "Security Device" you 35 | will see a field for "Nickname". 36 | {:class="t-img"} 37 | 3. Choose a name for your device. Use something that helps you remember 38 | which device you used. For example, you might use nicknames like "Mary's 39 | YubiKey" or "Naveen's iPhone". 40 | 4. Below the Nickname field, click **Register device**. 41 | 5. Your browser will prompt you to set up a device or a Passkey. This 42 | experience varies according to browser. Chrome tries to set up a Passkey 43 | that it manages, though you can select "Try another way" to use a USB 44 | hardware token. Safari asks you to enable iCloud Keychain, but you can 45 | click "Other Options" to use a hardware token. Other browsers may vary. 46 | 6. You will now see your security device on the screen above the Nickname 47 | field. 48 | -------------------------------------------------------------------------------- /using-webauthn-mfa-in-command-line.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Using WebAuthn multi-factor authentication in command line 4 | url: /using-webauthn-mfa-in-command-line 5 | previous: /using-mfa-in-command-line 6 | next: /using-otp-mfa-in-command-line 7 | --- 8 | How to use WebAuthn MFA with gem CLI. 9 | 10 | Multi-factor authentication (MFA) using WebAuthn works by using a 11 | removable hardware token or touch biometric / facial biometric capabilities built 12 | into your phone or computer. This is distinct from MFA based on typing or copying 13 | a code generated by an authentication app or password manager, called OTP. For 14 | OTP MFA see "[Using OTP multi-factor authentication in command line](/ using-otp-mfa-in-command-line)". 15 | 16 | When you have enabled WebAuthn MFA, we will ask you to perform authentication on certain 17 | commands based on your [authentication level](https://guides.rubygems.org/setting-up-multifactor-authentication/#authentication-levels). 18 | 19 | Enter your RubyGems.org credentials. 20 | Don't have an account yet? Create one at https://rubygems.org/sign_up 21 | Email: gem_author@example 22 | Password: 23 | 24 | [snip of API key setup] 25 | 26 | You have enabled multi-factor authentication. 27 | Please visit http://localhost:3000/webauthn_verification/Specification%,
12 | 'Specification' %>
13 | <%= description -%>
14 |
15 | <%
16 | sections = {}
17 | klass.each_section do |section, constants, attributes|
18 | sections[section] = attributes if section.title =~ /attributes/
19 | end
20 |
21 | all_methods = {}
22 | sections.to_a.reverse.each do |section, attributes|
23 | all_methods[section] = {}
24 | attributes.each do |attrib|
25 | all_methods[section][attrib.name] = attrib
26 | end
27 | klass.methods_by_type(section).each do |type, visibilities|
28 | visibilities.each do |visibility, methods|
29 | methods.each do |method|
30 | all_methods[section][method.name] = method
31 | end
32 | end
33 | end
34 | end
35 | -%>
36 |
37 | <% all_methods.each do |section, methods| %>
38 | ## <%= section.title %>
39 | <% methods.sort_by(&:first).each do |name, method| %>
40 | * [<%= name %>](#<%= name %>)
41 | <% end -%>
42 | <% end %>
43 |
44 | <% all_methods.each do |section, methods| -%>
45 |
46 | # <%= section.title %>
47 |
48 | <% methods.sort_by(&:first).each do |name, method|
49 | params =
50 | if method.params then
51 | params = method.params.delete '()'
52 |
53 | if params.empty? then
54 | ''
55 | else
56 | params = params.split(',').map { |param| "`#{param.strip}`" }.join ', '
57 | "(#{params})"
58 | end
59 | end
60 | -%>
61 |
62 |
63 |
64 | ## <%= name %><%= params %>
65 | <% description = method.description.strip
66 | description.gsub! %r%(.*?)%m, '\2'
67 | description.gsub! %r%Specification%,
68 | 'Specification' %>
69 | <%= description %>
70 | <% if method.aliases.any? -%>
71 | Also known as: **<%= method.aliases.map(&:name).join(", ") %>**
72 | <% end -%>
73 | <% end -%>
74 | <% end -%>
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/organizations/roles-and-permissions.md:
--------------------------------------------------------------------------------
1 | ---
2 | url: /organizations/roles-and-permissions
3 | title: Organization Roles and Permissions
4 | layout: default
5 | previous: /organizations/managing-members
6 | next: /organizations/transferring-gems
7 | ---
8 |
9 | # Organization Roles and Permissions
10 |
11 |
16 |
17 | Organizations use role-based access control to ensure team members have appropriate permissions. Understanding these roles helps you build an effective collaboration structure.
18 |
19 | ## Available Roles
20 |
21 | ### Owner
22 | The highest level of access. Owners have complete control over the organization.
23 |
24 | **Best for:** Organization founders, CTOs, or team leads who need full control.
25 |
26 | ### Admin
27 | Administrators handle day-to-day organization management.
28 |
29 | **Best for:** Senior developers, team managers, or trusted contributors who manage gems and team members.
30 |
31 | ### Maintainer
32 | The base level of organization membership with essential access.
33 |
34 | **Best for:** Developers who need to work with organization gems but don't require administrative privileges.
35 |
36 | ## Permission Comparison
37 |
38 | | Action | Owner | Admin | Maintainer |
39 | |--------|--------|--------|--------|
40 | | View organization info | ✓ | ✓ | ✓ |
41 | | Push gem versions | ✓ | ✓ | ✓ |
42 | | Yank gem versions | ✓ | ✓ | ✓ |
43 | | View member list | ✓ | ✓ | ✓ |
44 | | Invite members | ✓ | ✓ | ✗ |
45 | | Remove members | ✓ | ✓¹ | ✗ |
46 | | Change member roles | ✓ | ✓¹ | ✗ |
47 | | Add gems | ✓ | ✓ | ✗ |
48 | | Remove gems | ✓ | ✗ | ✗ |
49 | | Update organization | ✓ | ✗ | ✗ |
50 | | Delete organization | ✓ | ✗ | ✗ |
51 |
52 | ¹ *Admins cannot modify or remove Owners*
53 |
54 | ## Next Steps
55 |
56 | - Learn about [Managing Organization Members](/organizations/managing-members)
57 | - Understand [Transferring Gems](/organizations/transferring-gems) to organizations
58 |
59 | ---
60 |
61 | Questions about roles? Contact [support@rubygems.org](mailto:support@rubygems.org) for assistance.
62 |
--------------------------------------------------------------------------------
/javascripts/mobile-nav.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | // cache jQuery lookups into variables
3 | // so we don't have to traverse the DOM every time
4 | var sandwichIcon = $('.header__club-sandwich');
5 | var header = $('.header');
6 | var main = $('main');
7 | var footer = $('.footer');
8 | var signUpLink = $('.header__nav-link.js-sign-up-trigger');
9 | var signInLink = $('.hader__nav-link.js-sign-in-trigger');
10 | var navExpandedClass = 'mobile-nav-is-expanded';
11 | var headerSeach = $('.header__search');
12 | var headerLogo = $('.header__logo-wrap');
13 |
14 | // variable to support mobile nav tab behaviour
15 | // * skipSandwichIcon is for skipping sandwich icon
16 | // when you tab from "gem" icon
17 | // * tabDirection is for hiding and showing navbar
18 | // when you tab in and out
19 | var skipSandwichIcon = true;
20 | var tabDirection = true;
21 |
22 | function removeNavExpandedClass() {
23 | header.removeClass(navExpandedClass);
24 | main.removeClass(navExpandedClass);
25 | footer.removeClass(navExpandedClass);
26 | }
27 |
28 | function addNavExpandedClass() {
29 | header.addClass(navExpandedClass);
30 | main.addClass(navExpandedClass);
31 | footer.addClass(navExpandedClass);
32 | }
33 |
34 | function handleClick(event) {
35 | var isMobileNavExpanded = header.hasClass(navExpandedClass);
36 |
37 | event.preventDefault();
38 |
39 | if (isMobileNavExpanded) {
40 | removeNavExpandedClass();
41 | } else {
42 | addNavExpandedClass();
43 | }
44 | }
45 |
46 | function handleFocusIn() {
47 | if (skipSandwichIcon) {
48 | addNavExpandedClass();
49 | headerSeach.focus();
50 | skipSandwichIcon = false;
51 | } else {
52 | removeNavExpandedClass();
53 | headerLogo.focus();
54 | skipSandwichIcon = true;
55 | }
56 | }
57 |
58 | sandwichIcon.click(handleClick);
59 |
60 | sandwichIcon.on('focusin', handleFocusIn);
61 |
62 | signUpLink.on('focusin', function() {
63 | if (!tabDirection) {
64 | addNavExpandedClass();
65 | }
66 | });
67 |
68 | signUpLink.on('focusout', function() {
69 | if (tabDirection) {
70 | tabDirection = false;
71 | removeNavExpandedClass();
72 | } else {
73 | tabDirection = true;
74 | addNavExpandedClass();
75 | }
76 | });
77 | });
78 |
--------------------------------------------------------------------------------
/stylesheets/_base.scss:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header, hgroup,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | }
18 |
19 | article, aside, details, figcaption, figure,
20 | footer, header, hgroup, menu, nav, section {
21 | display: block;
22 | }
23 |
24 | body {
25 | background-color: $red;
26 | font: {
27 | family: "aktiv-grotesk-std", sans-serif;
28 | size: 15px;
29 | }
30 | }
31 |
32 | section, article {
33 | &:not(:last-of-type) {
34 | @extend %border;
35 | }
36 | }
37 |
38 | section {
39 | &:not(:last-of-type) {
40 | border-bottom-width: 5px;
41 | }
42 | }
43 |
44 | article {
45 | &:not(:last-of-type) {
46 | border-bottom-width: 1px;
47 | }
48 | }
49 |
50 | i, em {
51 | font-style: italic;
52 | }
53 |
54 | b, strong {
55 | font-weight: 800;
56 | }
57 |
58 | a {
59 | text-decoration: none;
60 | }
61 |
62 | li {
63 | list-style-type: none;
64 | }
65 |
66 | pre {
67 | overflow: scroll;
68 | overflow: auto\0;
69 | background-color: $black;
70 | code {
71 | @extend %font-smoothing;
72 | color: $white;
73 | }
74 | }
75 |
76 | @font-face {
77 | font: {
78 | family: "icomoon";
79 | weight: normal;
80 | style: normal;
81 | }
82 | src: url("/fonts/icomoon.eot");
83 | src: url("/fonts/icomoon.eot?#iefix") format("eot"), url("/fonts/icomoon.woff") format("woff"), url("/fonts/icomoon.ttf") format("truetype");
84 | }
85 |
86 | [data-icon]:before {
87 | content: attr(data-icon);
88 | font-family: "icomoon";
89 | @extend %font-smoothing;
90 | speak: none;
91 | }
92 |
93 | ::-webkit-input-placeholder {
94 | font-style: italic;
95 | }
96 |
97 | :-moz-placeholder {
98 | font-style: italic;
99 | }
100 |
101 | ::-moz-placeholder {
102 | font-style: italic;
103 | }
104 |
105 | :-ms-input-placeholder {
106 | font-style: italic;
107 | }
108 |
--------------------------------------------------------------------------------
/using-otp-mfa-in-command-line.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Using OTP multi-factor authentication in command line
4 | url: /using-otp-mfa-in-command-line
5 | previous: /using-webauthn-mfa-in-command-line
6 | next: /mfa-requirement-opt-in
7 | ---
8 | How to use OTP MFA with gem CLI.
9 |
10 | Multi-factor authentication (MFA) using OTP works by using an authenticator app on your phone
11 | to generate a one-time password (OTP) that you then enter at the command line. This
12 | documentation explains how to use OTP multi-factor authentication (MFA) if you have configured
13 | it. For WebAuthn instructions, see "[Using WebAuthn multi-factor authentication in command line](/using-webauthn-mfa-in-command-line)".
14 |
15 | When you have only enabled OTP MFA, and your MFA level is _UI and API_, we will ask to you
16 | to provide an OTP for `gem signin`, `gem push`, `gem owner --add` and `gem owner --remove`.
17 | Check [setting up multi-factor authentication](/setting-up-multifactor-authentication)
18 | for enabling MFA.
19 |
20 | This level requires a recent enough `gem` command as shipped with Ruby 2.6+,
21 | or [RubyGems 3.0+](https://rubygems.org/pages/download).
22 |
23 | You can preemptively pass an OTP code using `--otp` flag or else we will prompt
24 | for the OTP code when required:
25 |
26 | $ gem signin
27 | Enter your RubyGems.org credentials.
28 | Don't have an account yet? Create one at https://rubygems.org/sign_up
29 | Email: gem_author@example
30 | Password:
31 |
32 | You have enabled multi-factor authentication. Please enter OTP code.
33 | Code: 111111
34 | Signed in.
35 |
36 | Passing OTP as flag:
37 |
38 | $ gem signin --otp 111111
39 | Enter your RubyGems.org credentials.
40 | Don't have an account yet? Create one at https://rubygems.org/sign_up
41 | Email: gem_author@example
42 | Password:
43 |
44 | Signed in.
45 |
46 | Note that `gem signin` only fetches and stores your rubygems.org api key. `gem signin`
47 | is not equivalent to creating a user session. We will check for OTP code every time you
48 | use any of the commands mentioned above.
49 |
50 | Publishing a gem after signing in from cli:
51 |
52 | $ gem push hello-0.0.1.gem
53 | Pushing gem to https://rubygems.org...
54 | You have enabled multi-factor authentication. Please enter OTP code.
55 | Code: 111111
56 | Successfully registered gem: hello (0.0.1)
57 |
--------------------------------------------------------------------------------
/rubygems-org-api-v2.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: RubyGems.org API V2
4 | url: /rubygems-org-api-v2
5 | previous: /rubygems-org-api
6 | next: /rubygems-org-compact-index-api
7 | ---
8 | Get twice more info with API v2
9 |
10 | Gem Version Methods
11 | -------------------
12 |
13 | ### GET - `/api/v2/rubygems/[GEM NAME]/versions/[VERSION NUMBER].(json|yaml)`
14 |
15 | Returns a dictionary with versions details for a specific gem version.
16 |
17 | To return the version for a specific platform (e.g. "ruby", "java", "x86_64-linux"), use the `platform` query parameter.
18 |
19 |
20 | $ curl https://rubygems.org/api/v2/rubygems/coulda/versions/0.7.1.json
21 |
22 | {
23 | "name": "coulda",
24 | "downloads": 86573,
25 | "version": "0.7.1",
26 | "version_created_at": "2011-08-08T21:23:40.254Z",
27 | "version_downloads": 5754,
28 | "platform": "ruby",
29 | "authors": "Evan David Light",
30 | "info": "Behaviour Driven Development derived from Cucumber but as an internal DSL with methods for reuse",
31 | "licenses": null,
32 | "metadata": {
33 | "homepage_uri": "http://coulda.tiggerpalace.com"
34 | },
35 | "yanked": false,
36 | "sha": "777c3a7ed83e44198b0a624976ec99822eb6f4a44bf1513eafbc7c13997cd86c",
37 | "spec_sha": "57b863cff56029a0085eaf1b3416b701ed4fa75418d062358b45753e270c9ffa",
38 | "project_uri": "https://rubygems.org/gems/coulda",
39 | "gem_uri": "https://rubygems.org/gems/coulda-0.7.1.gem",
40 | "homepage_uri": "http://coulda.tiggerpalace.com",
41 | "wiki_uri": null,
42 | "documentation_uri": null,
43 | "mailing_list_uri": null,
44 | "source_code_uri": null,
45 | "bug_tracker_uri": null,
46 | "changelog_uri": null,
47 | "funding_uri": null,
48 | "dependencies": {
49 | "development": [],
50 | "runtime": [
51 | {
52 | "name": "yourdsl",
53 | "requirements": "~> 0.7"
54 | }
55 | ]
56 | },
57 | "built_at": "2011-08-08T04:00:00.000Z",
58 | "created_at": "2011-08-08T21:23:40.254Z",
59 | "description": "Behaviour Driven Development derived from Cucumber but as an internal DSL with methods for reuse",
60 | "downloads_count": 5754,
61 | "number": "0.7.1",
62 | "summary": "Test::Unit-based acceptance testing DSL",
63 | "rubygems_version": ">= 0",
64 | "ruby_version": null,
65 | "prerelease": false,
66 | "requirements": null
67 | }
68 |
--------------------------------------------------------------------------------
/.github/workflows/scorecard.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub. They are provided
2 | # by a third-party and are governed by separate terms of service, privacy
3 | # policy, and support documentation.
4 |
5 | name: Scorecard supply-chain security
6 | on:
7 | # For Branch-Protection check. Only the default branch is supported. See
8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
9 | branch_protection_rule:
10 | # To guarantee Maintained check is occasionally updated. See
11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
12 | schedule:
13 | - cron: '25 17 * * 4'
14 | push:
15 | branches: [ "main" ]
16 |
17 | # Declare default permissions as read only.
18 | permissions: read-all
19 |
20 | jobs:
21 | analysis:
22 | name: Scorecard analysis
23 | runs-on: ubuntu-latest
24 | permissions:
25 | # Needed to upload the results to code-scanning dashboard.
26 | security-events: write
27 | # Needed to publish results and get a badge (see publish_results below).
28 | id-token: write
29 |
30 | steps:
31 | - name: "Checkout code"
32 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
33 | with:
34 | persist-credentials: false
35 |
36 | - name: "Run analysis"
37 | uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
38 | with:
39 | results_file: results.sarif
40 | results_format: sarif
41 |
42 | # Public repositories:
43 | # - Publish results to OpenSSF REST API for easy access by consumers
44 | # - Allows the repository to include the Scorecard badge.
45 | # - See https://github.com/ossf/scorecard-action#publishing-results.
46 | publish_results: true
47 |
48 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
49 | # format to the repository Actions tab.
50 | - name: "Upload artifact"
51 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
52 | with:
53 | name: SARIF file
54 | path: results.sarif
55 | retention-days: 5
56 |
57 | # Upload the results to GitHub's code scanning dashboard (optional).
58 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard
59 | - name: "Upload to code-scanning"
60 | uses: github/codeql-action/upload-sarif@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
61 | with:
62 | sarif_file: results.sarif
63 |
--------------------------------------------------------------------------------
/trusted-publishing.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Trusted Publishing
4 | url: /trusted-publishing
5 | previous: /releasing-rubygems
6 | next: /trusted-publishing/adding-a-publisher
7 | ---
8 |
9 | Trusted Publishing is a term for using OpenID Connect (OIDC) to exchange short-lived identity tokens between a trusted third-party service and RubyGems.org.
10 | This allows obtaining short-lived API tokens in an automated environment (such as CI) without having to store long-lived API tokens or username/password credentials.
11 |
12 | For a quickstart guide, see:
13 |
14 | - [Adding a trusted publisher to an existing gem](/trusted-publishing/adding-a-publisher)
15 | - [Pushing a new gem with a trusted publisher](/trusted-publishing/pushing-a-new-gem)
16 |
17 | ## How it works
18 |
19 | Trusted publishing is a mechanism for uploading gems to RubyGems.org without using long-lived secret credentials.
20 |
21 | You don't need to be an OIDC expert to use trusted publishing, but it's helpful to understand the basics of how it works.
22 |
23 | 1. Certain platforms, such as GitHub Actions, are OIDC _identity providers_, meaning they can issue short-lived identity tokens that third parties can **strongly** verify came from the CI service (as well as the repository, workflow, and commit that triggered the build).
24 | 1. Gems on RubyGems.org can be configured to trust particular configurations from particular providers, making that configuration a trusted publisher for that gem.
25 | 1. Release automation (such as GitHub Actions) can exchange the identity token for a short-lived API token from RubyGems.org, provided the token matches any trusted publishers that have been configured on RubyGems.org.
26 | 1. The API token can be used only to push to the gems that are configured to trust the publisher, and only for a short period of time.
27 |
28 | This mechanism has significant security & usability advantages compared to traditional authentication mechanisms:
29 |
30 | - **Usability**: trusted publishing does not require manually creating & storing API tokens from RubyGems.org. The only manual step is configuring the trusted publisher on RubyGems.org.
31 | - **Security**: RubyGems.org's normal API tokens are long-lived, meaning an attacker who obtains one can use it indefinitely. Trusted publishing tokens are short-lived, meaning they can only be used for a short period of time.
32 |
33 | ## Further reading
34 |
35 | We highly reccomend checking out the excellent docs written by our friends over at PyPI for some more in-depth information on how Trusted Publishing works:
36 |
37 | - [PyPI: Security model and considerations](https://docs.pypi.org/trusted-publishers/security-model/)
38 |
--------------------------------------------------------------------------------
/trusted-publishing/pushing-a-new-gem.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Pushing a new gem with a trusted publisher
4 | url: /trusted-publishing/pushing-a-new-gem
5 | previous: /trusted-publishing/adding-a-publisher
6 | next: /trusted-publishing/releasing-gems
7 | ---
8 |
9 | Trusted publishers are not just for existing gems, they can also be used to push new gems!
10 |
11 | This helps reduce the friction for setting up fully automated publishing workflows for new gems,
12 | since the same workflow will work for the first released version of a gem as well as all future versions.
13 |
14 | To set up a trusted publisher for a new gem, you'll need to set up a "pending" trusted publisher
15 | under your RubyGems.org profile.
16 |
17 | The process is the same as for [adding a trusted publisher to an existing gem](/trusted-publishing/adding-a-publisher),
18 | except that you'll also need to specify a gem name.
19 |
20 | To configure a pending trusted publisher, go to your [pending trusted publisher page](https://rubygems.org/profile/oidc/pending_trusted_publishers)
21 |
22 | {:class="t-img"}
23 |
24 | Click the "Create" button, which will take you to the publisher configuration page.
25 |
26 | {:class="t-img"}
27 |
28 | For example, if you have a repository at `https://github.com/rubygems/sample-gem` with a release workflow at `push_gem.yml` and an environment named `release` that you would like to push to RubyGems.org as the `sample-gem` gem, you would enter the following values:
29 |
30 | {:class="t-img"}
31 |
32 | Once you click "Create Pending trusted publisher", your publisher will be registered and will appear in the list of pending publishers for your account.
33 |
34 | {:class="t-img"}
35 |
36 | From this point, the "pending" publisher will act like a "normal" publisher.
37 | After its first successful push, it will be converted to a "normal" trusted publisher for the new gem,
38 | and you will be added as the owner of the gem.
39 |
40 | Now that you've created a Trusted Publisher, [create a GitHub Actions workflow](/trusted-publishing/releasing-gems) to publish your gem.
41 | You can use [rubygem-await's release workflow](https://github.com/segiddins/rubygems-await/blob/main/.github/workflows/push_gem.yml) as a starting point.
42 |
--------------------------------------------------------------------------------
/stylesheets/modules/_search.scss:
--------------------------------------------------------------------------------
1 | %search__icon {
2 | position: absolute;
3 | cursor: pointer;
4 | }
5 |
6 | %search {
7 | -webkit-appearance: none;
8 | padding: 10px;
9 | background-color: $white;
10 | border: none;
11 | border-radius: 5px;
12 | font: {
13 | weight: 300;
14 | family: "aktiv-grotesk-std", sans-serif;
15 | }
16 | color: $black;
17 | outline: none;
18 | &::-webkit-input-placeholder {
19 | color: $red;
20 | }
21 | &:-moz-placeholder {
22 | color: $red;
23 | }
24 | &::-moz-placeholder {
25 | color: $red;
26 | }
27 | &:-ms-input-placeholder {
28 | color: $red;
29 | }
30 | }
31 |
32 | .header__search-wrap {
33 | position: relative;
34 | @extend %box-sizing;
35 | @media (max-width: 1019px) {
36 | margin-top: 8px;
37 | padding: {
38 | right: 10px;
39 | bottom: 8px;
40 | left: 16px;
41 | }
42 | @-moz-document url-prefix() {
43 | margin-top: 7px;
44 | padding-bottom: 6px;
45 | }
46 | }
47 | @media (min-width: 1020px) {
48 | margin: {
49 | top: 18px;
50 | right: 16px;
51 | left: 80px;
52 | }
53 | float: left;
54 | }
55 | @media (min-width: 1020px) and (max-width: 1139px) {
56 | width: 260px;
57 | }
58 | @media (min-width: 1140px) {
59 | width: 360px;
60 | }
61 | }
62 |
63 | .header__search-wrap--home {
64 | @extend .header__search-wrap;
65 | @media (min-width: 1020px) {
66 | display: none;
67 | }
68 | }
69 |
70 | .header__search__icon {
71 | @extend %search__icon;
72 | top: 6px;
73 | right: 5px;
74 | border: none;
75 | background-color: transparent;
76 | font: {
77 | size: 16px;
78 | family: "icomoon";
79 | }
80 | color: $red;
81 | @include transition-duration(.25s);
82 | @include transition-property(color);
83 | @media (max-width: 1019px) {
84 | right: 15px;
85 | }
86 | &:hover, &:focus {
87 | color: #fa3c29;
88 | outline: none;
89 | }
90 | }
91 |
92 | .header__search {
93 | @extend %search;
94 | @extend %box-sizing;
95 | padding: 8px 32px 8px 8px;
96 | width: 100%;
97 | box-shadow: 0 0 0px 3px rgba($black, .1);
98 | font-size: 15px;
99 | &::-webkit-input-placeholder {
100 | opacity: 1;
101 | color: $red;
102 | }
103 | &:-moz-placeholder {
104 | opacity: 1;
105 | color: $red;
106 | }
107 | &::-moz-placeholder {
108 | opacity: 1;
109 | color: $red;
110 | }
111 | &:-ms-input-placeholder {
112 | opacity: 1;
113 | color: $red;
114 | }
115 | &:hover, &:focus {
116 | & ~ .header__search__icon {
117 | color: $red;
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/stylesheets/modules/_footer.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | padding: {
3 | top: 48px;
4 | bottom: 48px;
5 | }
6 | position: relative;
7 | overflow: auto;
8 | border-top: 1px solid rgba($white, .3);
9 | background-color: $black;
10 | box-shadow: 0 -4px 0 0 $black;
11 | z-index: 2;
12 | width: 100%;
13 | @media (min-width: 780px) {
14 | min-height: 70vh;
15 | }
16 | }
17 |
18 | .l-wrap--footer {
19 | margin: {
20 | right: auto;
21 | left: auto;
22 | }
23 | max-width: 940px;
24 | @media (max-width: 579px) {
25 | width: 78%;
26 | }
27 | @media (min-width: 580px) {
28 | width: 90%;
29 | }
30 | }
31 |
32 | .footer__about {
33 | max-width: 550px;
34 | font-weight: 300;
35 | line-height: 1.66;
36 | color: lighten($d-gray, 18%);
37 | @media (max-width: 579px) {
38 | font-size: 15px;
39 | }
40 | @media (min-width: 580px) {
41 | font-size: 18px;
42 | }
43 | @media (min-width: 780px) {
44 | min-height: 222px;
45 | }
46 | }
47 |
48 | .footer__sponsors {
49 | margin: 60px auto 0;
50 | padding-top: 45px;
51 | width: 90%;
52 | max-width: 940px;
53 | text-align: center;
54 | border-top: 1px solid rgba(255, 255, 255, 0.1);
55 | }
56 |
57 | .footer__sponsor {
58 | margin: 0 26px 36px;
59 | display: inline-block;
60 | height: 100px;
61 | width: 90px;
62 | background: {
63 | image: url(/images/sponsors.png);
64 | size: 100%;
65 | }
66 | background-size: 100%;
67 | font: {
68 | style: italic;
69 | weight: 300;
70 | size: 12px;
71 | }
72 | text-align: center;
73 | color: $white;
74 | opacity: .4;
75 | @include transition-duration(.25s);
76 | @include transition-property(opacity);
77 | &:focus, &:hover {
78 | opacity: 1;
79 | }
80 | &:focus {
81 | outline: none;
82 | }
83 | }
84 |
85 | .footer__sponsor__ruby_central {
86 | background-position: 0 4px;
87 | }
88 |
89 | .footer__sponsor__dockyard {
90 | background-position: 0 -204px;
91 | }
92 |
93 | .footer__sponsor__dnsimple {
94 | background-position: 0 -291px;
95 | }
96 |
97 | .footer__sponsor__datadog {
98 | background-position: 0 -634px;
99 | }
100 |
101 | .footer__sponsor__runscope {
102 | background-position: 0 -469px;
103 | }
104 |
105 | .footer__sponsor__fastly {
106 | background-position: 0 -541px;
107 | }
108 |
109 | .footer__sponsor__honeybadger {
110 | background-position: 0 -717px;
111 | }
112 |
113 | .footer__sponsor__domainr {
114 | background-position: 0 -804px;
115 | }
116 |
117 | .footer__sponsor__whitesource {
118 | background-position: 0 -885px;
119 | }
120 |
121 | .footer__sponsor__logo {
122 | margin-top: 5px;
123 | width: 100%;
124 | }
125 |
--------------------------------------------------------------------------------
/trusted-publishing/adding-a-publisher.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Adding a trusted publisher to an existing gem
4 | url: /trusted-publishing/adding-a-publisher
5 | previous: /trusted-publishing
6 | next: /trusted-publishing/pushing-a-new-gem
7 | ---
8 |
9 | Adding a trusted publisher to a gem only requires a single setup step.
10 |
11 | On [your profile page](https://rubygems.org/profile/me), click the link to any gem you'd like to configure.
12 |
13 | {:class="t-img"}
14 |
15 | If you're a gem owner, you'll see a link to "Trusted publishers" on the right side of the page. Click that link.
16 |
17 | {:class="t-img t-img--small"}
18 |
19 | This will take you to the gem's trusted publishers page.
20 |
21 | {:class="t-img"}
22 |
23 | Click the "Create" button, which will take you to the publisher configuration page.
24 |
25 | {:class="t-img"}
26 |
27 | Providing the owner name, repository name, and GitHub Actions workflow name allows RubyGems to securely accept uploaded gems from the GitHub Actions infrastructure.
28 | If you have multiple workflows that push gems, you can create one Trusted Publisher for each workflow.
29 |
30 | The environment allows GitHub to constrain who can publish your gem if many people have access to the repository.
31 | We suggest using the [GitHub Action Environment](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-environments-for-deploymentenvironment) name "release", which we will use in our workflow examples on the next page.
32 |
33 | Once you click "Create Rubygem trusted publisher", your publisher will be registered and will appear in the list of trusted publishers for this gem.
34 |
35 | {:class="t-img"}
36 |
37 | Now, the `push.yml` workflow on `indirect/indirect-trusted-publishing` will be able to generate short-lived API tokens from RubyGems.org that are able to push to this gem.
38 |
39 | A repo and workflow can be registered to multiple gems. For example, the `release.yml` workflow from the `rails/rails` repo can be registered for both the `rails` and `activerecord` gems. Each gem can likewise allow multiple publishers, for example a single gem could allow both workflows `release-linux.yml` and `release-mac.yml`.
40 |
41 | Now that you've created a Trusted Publisher, [create a GitHub Actions workflow](/trusted-publishing/releasing-gems) to publish your gem.
42 | You can use [rubygem-await's release workflow](https://github.com/segiddins/rubygems-await/blob/main/.github/workflows/push_gem.yml) as a starting point.
43 |
--------------------------------------------------------------------------------
/setting-up-otp-mfa.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Setting up OTP (auth app) multi-factor authentication
4 | url: /setting-up-otp-mfa
5 | previous: /setting-up-webauthn-mfa
6 | next: /using-mfa-in-command-line
7 | ---
8 |
9 | Setting up one-time password (OTP) multi-factor authentication
10 |
11 | ## Prerequisite
12 |
13 | You should have an authenticator app (like [Google Authenticator](https://support.google.com/accounts/answer/1066447),
14 | [Authy](https://authy.com/download/), or [Authenticator Plus](https://www.authenticatorplus.com)) which
15 | supports time-based one-time password (TOTP) to scan the QR code and generate
16 | an access code. SMS-based authentication or recovery is **not** supported.
17 |
18 | ## Enabling OTP multi-factor authentication
19 |
20 | 1. Login to rubygems.org using your existing account and go to the [edit settings](https://rubygems.org/settings/edit) page.
21 | Click **register a new device** in the "Multi-factor Authentication" section.
22 | {:class="t-img"}
23 | 2. You will be redirected to a page with a QR code and a text box for verifying OTP
24 | code. Please use your authenticator to scan the QR code. A new account for rubygems.org will be
25 | added to your authenticator app as soon as the scan completes.
26 | You can also add a new account manually using "Account" and "Key" shown next to the QR code.
27 | Please make sure you choose the option "time based" as MFA type.
28 | On successful registration, you will see a 6-digit access code (30
29 | seconds expiry) in your authenticator app for your rubygems.org account.
30 | Enter the shown access code in the "OTP Code" text field and click **Enable**.
31 | {:class="t-img"}
32 | 3. If the code is correct and the QR code has not expired, on next page you will see a list of recovery
33 | codes. Please copy and store these codes in a safe place. You can use these recovery
34 | codes to access your account, should you ever lose your phone or accidentally delete the
35 | rubygems.org account from your authenticator app. Note that each recovery code can be used
36 | only once. Please reregister your authenticator app after using recovery code to
37 | login to rubygems.org (see [Using recovery codes and re-setup a previously enabled MFA](#using-recovery-codes-and-re-setup-a-previously-enabled-mfa)).
38 | {:class="t-img"}
39 | 4. Sign out and sign in again. Signing in will now ask for an OTP code.
40 | {:class="t-img"}
41 |
42 | Note: The Google Authenticator app only allows an MFA account to be installed on one device
43 | and there is no backup or cloud sync of the data. So if you lose or upgrade your phone, you'll
44 | have to set up MFA again on the new phone. On the other hand, the Authy and Authenticator Plus
45 | apps allow you to use multiple devices by providing cloud backups and cross-device sync
46 | capabilities.
47 |
--------------------------------------------------------------------------------
/name-your-gem.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Name your gem
4 | url: /name-your-gem
5 | previous: /gems-with-extensions
6 | next: /publishing
7 | ---
8 |
9 | Our recommendation on the use of "_" and "-" in your gem's name.
10 |
11 | Here are some examples of our recommendations for naming gems:
12 |
13 | Gem name | Require statement | Main class or module
14 | ---------------------- | -------------------------------- | -----------------------
15 | `ruby_parser` | `require 'ruby_parser'` | `RubyParser`
16 | `rdoc-data` | `require 'rdoc/data'` | `RDoc::Data`
17 | `net-http-persistent` | `require 'net/http/persistent'` | `Net::HTTP::Persistent`
18 | `net-http-digest_auth` | `require 'net/http/digest_auth'` | `Net::HTTP::DigestAuth`
19 |
20 | The main goal of these recommendations is to give the user some clue about
21 | how to require the files in your gem. Following these conventions also lets
22 | Bundler require your gem with no extra configuration.
23 |
24 | If you publish a gem on [rubygems.org][rubygems] it may be removed if the name
25 | is objectionable, violates intellectual property or the contents of the gem meet
26 | these criteria. You can report such a gem to [support@rubygems.org](mailto:support@rubygems.org)
27 | via email.
28 |
29 | [rubygems]: https://rubygems.org
30 |
31 | Use underscores for multiple words
32 | ----------------------------------
33 |
34 | If a class or module has multiple words, use underscores to separate them. This
35 | matches the file the user will require, making it easier for the user to start
36 | using your gem.
37 |
38 | Use dashes for extensions
39 | -------------------------
40 |
41 | If you're adding functionality to another gem, use a dash. This usually
42 | corresponds to a `/` in the require statement (and therefore your gem's
43 | directory structure) and a `::` in the name of your main class or module.
44 |
45 | Mix underscores and dashes appropriately
46 | ----------------------------------------
47 |
48 | If your class or module has multiple words and you're also adding functionality
49 | to another gem, follow both of the rules above. For example,
50 | [`net-http-digest_auth`][digest-gem] adds
51 | [HTTP digest authentication][digest-standard] to `net/http`.
52 | The user will `require 'net/http/digest_auth'` to use the extension
53 | (in class `Net::HTTP::DigestAuth`).
54 |
55 | [digest-gem]: https://rubygems.org/gems/net-http-digest_auth
56 | [digest-standard]: https://tools.ietf.org/html/rfc2617
57 |
58 | Don't use UPPERCASE letters
59 | ---------------------------
60 |
61 | OS X and Windows have case-insensitive filesystems by default. Users may
62 | mistakenly require files from a gem using uppercase letters which will be
63 | non-portable if they move it to a non-windows or OS X system. While this will
64 | mostly be a newbie mistake we don't need to be confusing them more than
65 | necessary.
66 |
67 | Credits
68 | -------
69 |
70 | This guide was expanded from [How to Name Gems][how-to-name-gems] by Eric Hodel.
71 |
72 | [how-to-name-gems]: https://web.archive.org/web/20130821183311/https://blog.segment7.net/2010/11/15/how-to-name-gems
73 |
--------------------------------------------------------------------------------
/organizations/transferring-gems.md:
--------------------------------------------------------------------------------
1 | ---
2 | url: /organizations/transferring-gems
3 | title: Transferring Gems to Organizations
4 | layout: default
5 | previous: /organizations/roles-and-permissions
6 | next: /credits
7 | ---
8 |
9 | # Transferring Gems to Organizations
10 |
11 |
16 |
17 | Move your gems from individual ownership to organization management for better collaboration and continuity. This guide covers the transfer process, requirements, and best practices.
18 |
19 | ## Before You Transfer
20 |
21 | ### Requirements
22 |
23 | To transfer a gem, you need:
24 |
25 | - **Owner permissions** on the gem
26 | - **Admin or Owner role** in the target organization
27 | - **MFA enabled** on your account
28 |
29 | ### Important Considerations
30 |
31 | **Ownership changes are significant:**
32 | - Individual owners lose direct gem access
33 | - Organization members manage the gem based on roles
34 | - The organization name appears as the gem owner
35 | - Transfer cannot be reserved once completed
36 |
37 | **Plan your transfer:**
38 | - Notify co-owners before transferring
39 | - Document the transfer for your team
40 | - Update gem documentation with new ownership
41 |
42 | ## The Transfer Process
43 |
44 | Transferring a rubygem to an organization works very similarly to creating a new organization. Follow these steps:
45 |
46 | Navigate to the rubygems you want to transfer and click the **Transfer to Organization** link.
47 |
48 | ### Step 1: Select destination Organization
49 |
50 | Select the organization you want to transfer your gem from the dropdown menu.
51 |
52 | ### Step 2: Invite Team Members
53 |
54 | Add your collaborators to the Organization:
55 |
56 | **Automatic Suggestions:** We'll suggest users who co-own your selected gems. This makes it easy to maintain existing collaborations.
57 |
58 | **Existing Members:** If the user is already a member of the organization, they will show as
59 | already being a member.
60 |
61 | **Assign Roles:** Choose the appropriate role for each member:
62 |
63 | - **Owner**: Full organization control
64 | - **Admin**: Gem and member management
65 | - **Maintainer**: Basic access and gem operations
66 | - **Outside Collaborator**: Limited access, retains personal ownership of gems and is not a member of the orgnization
67 |
68 | ### Step 4: Confirm and Create
69 |
70 | Review your organization setup:
71 |
72 | - Organization name and handle
73 | - Selected gems for transfer
74 | - Invited members and their roles
75 |
76 | Click **Transfer** to finalize the migration. You'll see:
77 |
78 | - Your gem has been transferred to the selected organization.
79 | - New members receive an email to join your Organization.
80 | - You're redirected to your organization page
81 |
82 | ## Need Help?
83 |
84 | - See [Managing Members](/organizations/managing-members) guide
85 | - Contact [support@rubygems.org](mailto:support@rubygems.org) for assistance
86 |
87 | ---
88 |
89 | Transfer gems confidently to enable better collaboration.
90 |
--------------------------------------------------------------------------------
/_plugins/alias_generator.rb:
--------------------------------------------------------------------------------
1 | # Alias Generator for Posts.
2 | #
3 | # Generates redirect pages for posts with aliases set in the YAML Front Matter.
4 | #
5 | # Place the full path of the alias (place to redirect from) inside the
6 | # destination post's YAML Front Matter. One or more aliases may be given.
7 | #
8 | # Example Post Configuration:
9 | #
10 | # ---
11 | # layout: post
12 | # title: "How I Keep Limited Pressing Running"
13 | # alias: /post/6301645915/how-i-keep-limited-pressing-running/index.html
14 | # ---
15 | #
16 | # Example Post Configuration:
17 | #
18 | # ---
19 | # layout: post
20 | # title: "How I Keep Limited Pressing Running"
21 | # alias: [/first-alias/index.html, /second-alias/index.html]
22 | # ---
23 | #
24 | # Author: Thomas Mango
25 | # Site: http://thomasmango.com
26 | # Plugin Source: http://github.com/tsmango/jekyll_alias_generator
27 | # Site Source: http://github.com/tsmango/thomasmango.com
28 | # PLugin License: MIT
29 |
30 | module Jekyll
31 |
32 | class AliasGenerator < Generator
33 |
34 | def generate(site)
35 | @site = site
36 |
37 | process_posts
38 | process_pages
39 | end
40 |
41 | def process_posts
42 | @site.posts.each do |post|
43 | generate_aliases(post.url, post.data['alias'])
44 | end
45 | end
46 |
47 | def process_pages
48 | @site.pages.each do |page|
49 | generate_aliases(page.destination('').gsub(/index\.(html|htm)$/, ''), page.data['alias'])
50 | end
51 | end
52 |
53 | def generate_aliases(destination_path, aliases)
54 | alias_paths ||= Array.new
55 | alias_paths << aliases
56 | alias_paths.compact!
57 |
58 | alias_paths.flatten.each do |alias_path|
59 | alias_path = alias_path.to_s
60 |
61 | alias_dir = File.extname(alias_path).empty? ? alias_path : File.dirname(alias_path)
62 | alias_file = File.extname(alias_path).empty? ? "index.html" : File.basename(alias_path)
63 |
64 | fs_path_to_dir = File.join(@site.dest, alias_dir)
65 | alias_index_path = File.join(alias_dir, alias_file)
66 |
67 | FileUtils.mkdir_p(fs_path_to_dir)
68 |
69 | File.open(File.join(fs_path_to_dir, alias_file), 'w') do |file|
70 | file.write(alias_template(destination_path))
71 | end
72 |
73 | (alias_index_path.split('/').size + 1).times do |sections|
74 | @site.static_files << Jekyll::AliasFile.new(@site, @site.dest, alias_index_path.split('/')[0, sections].join('/'), '')
75 | end
76 | end
77 | end
78 |
79 | def alias_template(destination_path)
80 | <<-EOF
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | EOF
90 | end
91 | end
92 |
93 | class AliasFile < StaticFile
94 | require 'set'
95 |
96 | def destination(dest)
97 | File.join(dest, @dir)
98 | end
99 |
100 | def modified?
101 | return false
102 | end
103 |
104 | def write(dest)
105 | return true
106 | end
107 | end
108 | end
--------------------------------------------------------------------------------
/setting-up-multifactor-authentication.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Setting up multi-factor authentication
4 | url: /setting-up-multifactor-authentication
5 | previous: /run-your-own-gem-server
6 | next: /setting-up-webauthn-mfa
7 | ---
8 |
9 | Want to better protect your RubyGems.org account?
10 |
11 | Your RubyGems.org account is important! Unauthorized access of your account
12 | can lead to irrevocable damage to your gem's reputation. We highly recommend
13 | that you enable MFA for both UI and API. When enabled, this will mean that
14 | you need to use MFA for signing into RubyGems.org and when running `gem signin`,
15 | `push`, `owner --add`, `owner --remove` and `yank`.
16 |
17 | You may enable MFA using [WebAuthn](/setting-up-webauthn-mfa) or by
18 | using [one-time passwords (OTP)](/setting-up-otp-mfa).
19 |
20 | ## Authentication levels
21 |
22 | When you register a new device or enable MFA for the first time, we will enable
23 | MFA for both the UI and the API. If you go to the "Edit Settings" page again, in the "Multi-factor Authentication" section, you
24 | will see a dropdown menu with these options:
25 |
26 | - **UI and gem signin**: UI operations and `gem signin` will require OTP code.
27 | - **UI and API**: UI operations, `gem signin`, `push`, `owner --add` and `owner --remove` will require OTP code.
28 |
29 | **UI only** was previously a valid MFA level. However, it has been removed, and only accounts that are currently at that level will still see it in the dropdown.
30 |
31 | Note: If you are on the **UI and gem signin** authentication level,
32 | you can selectively enable MFA on specific API keys (see [API key scopes](https://guides.rubygems.org/api-key-scopes/#enable-mfa-on-specific-api-keys)).
33 | This is different from the **UI and API** level as MFA is enabled on all API keys by default and cannot be selectively enabled.
34 |
35 | Steps to change your MFA level:
36 |
37 | 1. Sign in and go to the _edit settings_ page. If you have enabled MFA for your account, in the "Multi-factor Authentication" section, you will see a dropdown menu. Select your intended option, and click **Update**.
38 | {:class="t-img"}
39 | 2. You will be prompted to use your MFA device to authorize the MFA level change.
40 | {:class="t-img"}
41 |
42 | ## Using recovery codes to reconfigure previously enabled MFA
43 |
44 | You might be in a situation where you no longer have access to your MFA device.
45 |
46 | In this situation, you'll need your recovery codes to gain access to your RubyGems.org account.
47 | Each recovery code can *only be used once* and you may need up to *2 recovery codes* to re-setup
48 | a previously enabled MFA RubyGems.org account on a new device.
49 |
50 | 1. To login into your account, enter an unused recovery code as the OTP code when prompted.
51 | 2. To reconfigure an [authenticator app](https://rubygems.org/settings/edit#authenticator-app), you'll need to use a recovery code to remove the current authenticator app. Then, you are able to enable and configure your authenticator app again. For security devices, you are able to associate a new security device to your account in the [security devices section](https://rubygems.org/settings/edit#security-device).
52 |
--------------------------------------------------------------------------------
/rubygems-org-rate-limits.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: RubyGems.org rate limits
4 | url: /rubygems-org-rate-limits
5 | previous: /rubygems-org-api-v2
6 | next: /api-key-scopes
7 | ---
8 |
9 | Why are you seeing 429 responses?
10 |
11 | Load balancer rate limits
12 | ----
13 |
14 | To protect the RubyGems.org service from abuse, both intentionally and unintentionally, we have rate limits in place for some of our endpoints. Some endpoints may be cached by our CDN at times and therefore, _may_ allow higher request rates. The following is a general guideline for the rate limit rules.
15 |
16 | * API and website: 10 requests per second
17 | * Dependency API: 15 requests per second
18 |
19 | Application rate limits
20 | ----
21 |
22 | We use [rack-attack](https://github.com/kickstarter/rack-attack) for throttling clients attempting brute force on login, signup, and MFA endpoints. Additionally, we rate limit endpoints which send email to prevent abuse of our paid email service. When you have hit one of the application rate limits, you will see `Retry-After` header in the response with seconds until the rate limit resets. Unless mentioned, all rate limits are on client IP.
23 |
24 | ## Endpoints with 100 requests/10 minutes rate limit
25 |
26 | * User sign in - `POST /session`
27 | * User sign up - `POST /users`
28 | * Password reset request - `POST /passwords`
29 | * Profile update - `PATCH /profile`
30 | * Profile delete - `DELETE /profile`
31 | * Email confirmation request - `POST /email_confirmations`
32 |
33 | ## Rate limits with exponential backoff
34 |
35 | It is not possible to brute force your MFA code in a single time window. However, an attacker's chance of successfully guessing the code at least once increases when the brute force is attempted over an extended period. You can read more about this [here](https://security.stackexchange.com/a/185917) and check our calculation of the backoff period [here](https://github.com/rubygems/rubygems.org/pull/2330#issuecomment-643931531).
36 | Following endpoints have rate limits of **300 requests/5 minutes** and **600 requests/25 hours**:
37 |
38 | * OTP verification on sign in - `POST /session/mfa_create`
39 | * OTP verification on password reset - `POST /users/:user_id/password/mfa_edit`
40 | * Registering a new MFA device - `POST /multifactor_auth`
41 | * Updating or disabling MFA level - `PUT /multifactor_auth`
42 | * Yanking a gem - `DELETE /api/v1/gems/yank`
43 | * Adding an owner - `POST /api/v1/gems/:rubygem_id/owners`
44 | * Removing owner - `DELETE /api/v1/gems/:rubygem_id/owners`
45 | * Show API key (`gem signin`) - `GET /api/v1/api_key`
46 |
47 | ## Gem push rate limit
48 |
49 | `POST /api/v1/gems` has following two rate limits:
50 |
51 | * 400 requests/1 hour
52 | * 300 requests/5 minutes and 600 requests/25 hours on *failed requests* (response status not equal to 200).
53 |
54 | ## Miscellaneous rate limits
55 |
56 | * 10 request/10 minutes on gem yank - `DELETE /api/v1/gems/yank`
57 | * 100 request/10 minutes/email or username on sign in - `POST /session`
58 | * 100 request/10 minutes/email or username on API key show - `GET /api/v1/api_key`
59 | * 10 request/10 minutes/email on password reset request - `POST /passwords`
60 | * 10 request/10 minutes/email on email confirmation request - `POST /email_confirmations`
61 |
62 | The RubyGems.org team may occasionally blackhole user IP addresses for extreme cases to protect the platform.
63 | If you think this has happened to you, please email to [support@rubygems.org](mailto:support@rubygems.org), and we'll be happy to look at it.
64 |
--------------------------------------------------------------------------------
/spec/options_list_markdownizer_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative 'spec_helper'
2 | require_relative '../lib/options_list_markdownizer'
3 |
4 | describe OptionsListMarkdownizer do
5 | let(:subject) { OptionsListMarkdownizer.new }
6 |
7 | [
8 | # make sure that lines without options don't suffer any harm
9 | [''],
10 | ['nothing interesting'],
11 |
12 | # real-life examples of option lines
13 | [
14 | " --norc Avoid loading any .gemrc file\n",
15 | " `--norc` Avoid loading any .gemrc file\n",
16 | 'long option only',
17 | ],
18 | [
19 | " --host HOST Yank from another gemcutter-compatible host\n",
20 | " `--host HOST` Yank from another gemcutter-compatible host\n",
21 | 'long option with argument',
22 | ],
23 | [
24 | " -h, --help Get help on this command\n",
25 | " `-h, --help` Get help on this command\n",
26 | 'short and long option',
27 | ],
28 | [
29 | " -s, --source URL Append URL to list of remote gem sources\n",
30 | " `-s, --source URL` Append URL to list of remote gem sources\n",
31 | 'short, long, argument',
32 | ],
33 | [
34 | " -b, --build EMAIL_ADDR Build private key and self-signed\n",
35 | " `-b, --build EMAIL_ADDR` Build private key and self-signed\n",
36 | 'argument with underscore',
37 | ],
38 | [
39 | " -g, --file [FILE] Read from a gem dependencies API file and\n",
40 | " `-g, --file [FILE]` Read from a gem dependencies API file and\n",
41 | 'short, long, optional argument',
42 | ],
43 | [
44 | " -s, --spec-dir a,b,c Search for gems under specific paths\n",
45 | " `-s, --spec-dir a,b,c` Search for gems under specific paths\n",
46 | 'short, long and argument list',
47 | ],
48 | [
49 | " -V, --[no-]verbose Set the verbose level of output\n",
50 | " `-V, --[no-]verbose` Set the verbose level of output\n",
51 | 'short option and boolean switch long',
52 | ],
53 | [
54 | " -K, --private-key KEY Key for --sign or --build. -g/--file can be used.\n",
55 | " `-K, --private-key KEY` Key for `--sign` or `--build`. `-g`/`--file` can be used.\n",
56 | 'long option in description',
57 | ],
58 | [
59 | " -R, --re-sign Re-signs the certificate from -C with the key from -K\n",
60 | " `-R, --re-sign` Re-signs the certificate from `-C` with the key from `-K`\n",
61 | 'short option in description',
62 | ],
63 | [
64 | " and the certificate from -C\n",
65 | " and the certificate from `-C`\n",
66 | 'short option in description continuation',
67 | ],
68 | [
69 | " -C PATH Run as if gem build was started in block
87 | html_string += line
88 | else
89 | html_string += line.gsub("<", "<").gsub(">", ">")
90 | end
91 | html_string += "\n"
92 | end
93 | end
94 | html_string[0..-2]
95 | end
96 |
97 | def argument_list_item(string)
98 | if string =~ /^(\S+)(.*)/
99 | string = "*#{$1}* - #{$2}"
100 | end
101 | htmlify("* #{string}")
102 | end
103 |
104 | def options_list(command)
105 | OptionsListMarkdownizer.new.call command
106 | end
107 |
108 | filename = "command-reference.erb"
109 |
110 | erbio = ERB.new File.read(filename), trim_mode: '-'
111 | content = erbio.result(binding).gsub(ENV["HOME"], "~")
112 |
113 | File.write 'command-reference.md', content
114 | end
115 |
116 | desc "serve documentation on http://localhost:4000"
117 | task :server do
118 | pids = [
119 | spawn('jekyll', 'serve', '4000'),
120 | spawn('sass', '--watch', 'stylesheets:stylesheets'),
121 | ]
122 |
123 | trap "INT" do
124 | Process.kill "INT", *pids
125 | exit 1
126 | end
127 |
128 | trap "TERM" do
129 | Process.kill "TERM", *pids
130 | exit 1
131 | end
132 |
133 | pids.each do |pid|
134 | Process.waitpid pid
135 | end
136 | end
137 |
138 | desc 'build documentation and display it on http://localhost:4000'
139 | task default: %w[spec_guide command_guide server]
140 |
--------------------------------------------------------------------------------
/organizations/managing-members.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Managing Organization Members
3 | layout: default
4 | url: /organizations/managing-members
5 | previous: /organizations/getting-started
6 | next: /organizations/roles-and-permissions
7 | ---
8 |
9 | # Managing Organization Members
10 |
11 |
16 |
17 | This guide covers inviting new members, managing existing ones, and handling common membership scenarios.
18 |
19 | ## Viewing Members
20 |
21 | Access your member list from your organization page:
22 |
23 | 1. Navigate to `rubygems.org/organizations/your-handle`
24 | 2. Click **Members** in the navigation
25 | 3. View all current members with their roles and join dates
26 |
27 | The member list shows:
28 | - Member username
29 | - Current role (Owner, Admin, or Maintainer)
30 |
31 | ## Inviting New Members
32 |
33 | ### Who Can Invite?
34 | - **Owners** can invite members at any level
35 | - **Admins** can invite new Admins and Maintainers
36 | - **Maintainers** cannot send invitations
37 |
38 | ### Sending Invitations
39 |
40 | 1. Click **Invite** from the members page
41 | 2. Enter the invitee's username
42 | 3. Select their role
43 | 5. Click **Invite**
44 |
45 | ### Invitation Process
46 |
47 | **What happens next:**
48 | - Invitee receives an email with a link to join the Organization
49 | - MFA must be enabled before joining
50 | - Invitation expires after 7 days
51 | - You'll see pending invitations in your member list
52 |
53 | **Tracking invitations:**
54 | - Pending invitations appear with a "Pending" status
55 | - See when invitations were sent
56 | - Resend or cancel pending invitations
57 |
58 | ## Managing Existing Members
59 |
60 | ### Changing Roles
61 |
62 | Adjust member permissions as responsibilities evolve:
63 |
64 | 1. Find the member in your list
65 | 3. Select the new role from the dropdown
66 | 4. Confirm the change
67 |
68 | **Important considerations:**
69 | - Only Owners can change Owner roles
70 | - Admins cannot modify Owner permissions
71 | - Changes take effect immediately
72 |
73 | ### Removing Members
74 |
75 | When team members leave or no longer need access:
76 |
77 | 1. Locate the member to remove
78 | 2. Click **Delete**
79 | 3. Confirm the removal
80 |
81 | **Removal rules:**
82 | - Owners can remove anyone except themselves
83 | - Admins can remove Maintainers and other Admins
84 | - Members cannot remove themselves
85 | - Removed members lose all organization access immediately
86 |
87 | ### API and Automation
88 |
89 | Currently, managing Organization Members must be done through the web interface. API support may be added in future.
90 |
91 | ## Troubleshooting
92 |
93 | ### Invitation Not Received
94 | - Check spam/junk folders
95 | - Resend invitation if needed
96 | - Confirm recipient has email access
97 |
98 | ### Cannot Change Roles
99 | - Verify you have appropriate permissions
100 | - Owners cannot be modified by Admins
101 | - Contact an Owner for help
102 |
103 | ### Member Cannot Access Gems
104 | - Confirm MFA is enabled
105 | - Verify membership is active (not pending)
106 | - Check organization gem list
107 | - Review member's role permissions
108 |
109 | ### Accidental Removal
110 | - Removed members must be re-invited
111 | - Previous role not automatically restored
112 | - Act quickly to minimize disruption
113 |
114 | ## Getting Help
115 |
116 | - Review our [Roles and Permissions](/organizations/roles-and-permissions) guide
117 | - Contact [support@rubygems.org](mailto:support@rubygems.org) for complex issues
118 |
119 | ---
120 |
121 | Keep your team organized and secure with proper member management.
122 |
--------------------------------------------------------------------------------
/managing-owners-using-ui.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Managing Owners via UI
4 | url: /managing-owners-using-ui
5 | previous: /security
6 | next: /removing-a-published-gem
7 | ---
8 |
9 | How to add or remove owners to your gem using the web UI?
10 |
11 | Similar to `gem owner --add` and `gem owner --remove` commands of the gem CLI,
12 | you can add or remove the owners of the gem you own using the web UI.
13 |
14 | Ownership section of your gem
15 | -----------------------------
16 |
17 | If you are an owner of a gem,
18 | a link to *Ownership* will be visible in the Links section, as shown in the image below.
19 | {:class="t-img t-img--small"}
20 |
21 | You will be asked to enter your account's password when you visit this page. You won't be prompted for the password confirmation for the next 10 minutes. It is a precautionary measure to ensure that no one abuses your unattended logged in session.
22 | {:class="t-img"}
23 |
24 | You will be able to see the confirmation status, MFA level and the user who authorized the owner's addition. Confirmed owners will have the time they confirmed their ownership. *ADDED BY* column may be empty for owners who were added before we started tracking authorizers.
25 | {:class="t-img"}
26 |
27 | Adding user as an owner to your gem
28 | --------------------------------
29 | Step: 1
30 | Enter the email or handle of the user in the text field labels *Email/Handle*
31 | Select the role that best suits the user, see [Owner & Maintainer Roles](#owner--maintainer-roles) for more details.
32 | Finally, click *Add Owner*.
33 |
34 | Step: 2
35 | The user added as an owner will be sent an email with a link to confirm the ownership.
36 | The ownership will be confirmed after the user clicks on the confirmation link within `48 hours`.
37 | On confirmation, all the existing owners will be notified about the owner addition.
38 |
39 | `Note that` the user won't have access to the gem until they confirm the ownership addition.
40 |
41 | Owner & Maintainer Roles
42 | ------------------------
43 | When managing owners, you have the option to select a role, either Owner or Maintainer. Owners have full control over the gem, including the ability to add or remove owners. Maintainers, have the ability to publish and yank gem versions, but cannot manage users, or configure gem security settings.
44 |
45 | Owners & Maintainers have access to the following permissions:
46 |
47 | | Permission | Owner | Maintainer |
48 | |:--------------------------------------:|:-----:|:----------:|
49 | | Can publish new gem versions | ✅ | ✅ |
50 | | Can yank gem versions | ✅ | ✅ |
51 | | Can add or remove owners | ✅ | ❌ |
52 | | Configure OIDC and Trusted Publishing | ✅ | ❌ |
53 |
54 |
55 | Updating an owner role
56 | ----------------------
57 | To update the role of an owner, visit `https://rubygems.org/gems//owners` and click on the *Edit* button of the
58 | corresponding user. You can select the role of the user edit page and click on *Update Owner*.
59 |
60 | Resend ownership confirmation link
61 | ----------------------------------
62 | In case you weren't able to confirm the ownership within 48 hours,
63 | you can resend the confirmation link by visiting the gem page.
64 | `https://rubygems.org/gems/`
65 |
66 | {:class="t-img t-img--small"}
67 |
68 | Clicking on *Resend Confirmation* will send an email with the confirmation link
69 | to your email address.
70 |
71 | Removing owner from your gem
72 | ----------------------------
73 | To remove an owner from your gem, visit `https://rubygems.org/gems//owners`
74 | and click on the *Remove Owner* button of the corresponding user.
75 | A notification email will be sent to the removed user.
76 |
--------------------------------------------------------------------------------
/organizations/getting-started.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Getting Started with Organizations
4 | url: /organizations/getting-started
5 | previous: /organizations
6 | next: /organizations/managing-members
7 | ---
8 |
9 |
14 |
15 | # Getting Started with Organizations
16 |
17 | Create your first Organization and start collaborating on RubyGems.org in minutes.
18 |
19 | ## Before You Begin
20 |
21 | To create an organization, you'll need:
22 |
23 | - A RubyGems.org account with **multi-factor authentication (MFA)** enabled
24 | - At least one gem where you're listed as an owner
25 |
26 | ## Creating Your Organization
27 |
28 | Navigate to your [dashboard](https://rubygems.org/dashboard) and click **Create Organization**.
29 |
30 | ### Step 1: Set Organization Details
31 |
32 | Provide your Organization's information:
33 |
34 | - **Handle**: Your unique identifier (selected from a list of gems the current user is listed as an owner of)
35 | - **Display Name**: How your Organization appears publicly (2-255 characters)
36 |
37 | The handle becomes part of your organization URL: `rubygems.org/organizations/your-handle`
38 |
39 | ### Step 2: Select Gems to Transfer
40 |
41 | If you own multiple gems, choose which ones to transfer to the Organization:
42 |
43 | - Only gems where you're an owner appear in the list
44 | - Gems already belonging to other organizations are excluded
45 | - You can transfer additional gems later
46 |
47 | **Important:** Once transferred, gems belong to the Organization. Individual ownership is replaced by organization membership.
48 |
49 | ### Step 3: Invite Team Members
50 |
51 | Add your collaborators to the Organization:
52 |
53 | **Automatic Suggestions:** We'll suggest users who co-own your selected gems. This makes it easy to maintain existing collaborations.
54 |
55 | **Assign Roles:** Choose the appropriate role for each member:
56 |
57 | - **Owner**: Full organization control
58 | - **Admin**: Gem and member management
59 | - **Maintainer**: Basic access and gem operations
60 | - **Outside Collaborator**: Limited access, retains personal ownership of gems and is not a member of the orgnization
61 |
62 | ### Step 4: Confirm and Create
63 |
64 | Review your organization setup:
65 |
66 | - Organization name and handle
67 | - Selected gems for transfer
68 | - Invited members and their roles
69 |
70 | Click **Create Organization** to finalize your Organization. You'll see:
71 |
72 | - Your Organization is created
73 | - Selected gems are transferred
74 | - Members receive an email to join your Organization.
75 | - You're redirected to your organization page
76 |
77 | ## After Creation
78 |
79 | ### Immediate Next Steps
80 |
81 | 1. **Wait for Invitations**: Members must accept their invitations to join the Organization
82 | 2. **Configure Settings**: Visit organization settings to customize further
83 | 3. **Transfer More Gems**: Add additional gems as needed
84 |
85 | ### Managing Your Organization
86 |
87 | Access your Organization at `rubygems.org/organizations/your-handle` to:
88 |
89 | - View organization gems
90 | - Manage members
91 | - Update settings
92 | - Monitor activity
93 |
94 | ### API and Automation
95 |
96 | Currently, onboarding an Organization must be done through the web interface. API support may be added in future.
97 |
98 | ## Troubleshooting
99 |
100 | ### Can't Create Organization?
101 | - Verify MFA is enabled on your account
102 | - Ensure you own at least one gem (for gem-based organizations)
103 | - Check that your desired handle isn't already taken
104 |
105 | ### Invitation Issues?
106 | - Invitations expire after a set period
107 | - Members must have RubyGems.org accounts
108 | - Check spam folders for invitation emails
109 |
110 | ### Need More Help?
111 |
112 | - Review our [Roles and Permissions](/organizations/roles-and-permissions) guide
113 | - Learn about [Managing Members](/organizations/managing-members)
114 | - Contact [support@rubygems.org](mailto:support@rubygems.org) for assistance
115 |
116 | ---
117 |
118 | Ready to collaborate? [Create your Organization](https://rubygems.org/organizations/new) now.
119 |
--------------------------------------------------------------------------------
/stylesheets/_type.scss:
--------------------------------------------------------------------------------
1 | .t-display {
2 | font-weight: 200;
3 | @media (max-width: 929px) {
4 | font-size: 30px;
5 | }
6 | @media (min-width: 930px) {
7 | font-size: 60px;
8 | }
9 | }
10 |
11 | .t-hidden {
12 | position: absolute !important;
13 | height: 1px;
14 | width: 1px;
15 | overflow: hidden;
16 | clip: rect(1px 1px 1px 1px);
17 | clip: rect(1px, 1px, 1px, 1px);
18 | }
19 |
20 | .t-gray {
21 | color: darken($gray, 13%);
22 | }
23 |
24 | .t-uppercase {
25 | text-transform: uppercase;
26 | }
27 |
28 |
29 |
30 | // Links
31 |
32 | .t-link {
33 | color: $red;
34 | @include transition-duration(.25s);
35 | @include transition-property(color);
36 | &:focus, &:hover, &:active {
37 | color: rgba($red, .7);
38 | }
39 | &:focus {
40 | outline: none;
41 | }
42 | }
43 |
44 |
45 |
46 | // Body
47 |
48 | .t-body {
49 | p, ol li, ul li {
50 | font: {
51 | weight: 300;
52 | size: 18px;
53 | }
54 | line-height: 1.66;
55 | }
56 |
57 | p, ul, ol, pre, table {
58 | margin-bottom: 30px;
59 | }
60 |
61 | a {
62 | @extend .t-link;
63 | word-wrap: break-word;
64 | }
65 |
66 | b, strong {
67 | font-weight: 500;
68 | }
69 |
70 | h1, h2 {
71 | margin-bottom: 24px;
72 | font: {
73 | weight: 800;
74 | size: 18px;
75 | }
76 | text-transform: uppercase;
77 | line-height: 1.66;
78 | code {
79 | text-transform: none;
80 | }
81 | }
82 |
83 | h2 {
84 | margin: {
85 | top: 30px;
86 | bottom: 30px;
87 | }
88 | padding-top: 30px;
89 | border: {
90 | top-style: solid;
91 | color: $gray;
92 | }
93 | &:first-of-type {
94 | border-top-width: 5px;
95 | }
96 | &:not(:first-of-type) {
97 | border-top-width: 1px;
98 | }
99 | }
100 |
101 | h3 {
102 | margin-bottom: 24px;
103 | font: {
104 | weight: 500;
105 | style: italic;
106 | size: 18px;
107 | }
108 | line-height: 1.66;
109 | }
110 |
111 | hr {
112 | margin: {
113 | top: 30px;
114 | bottom: 30px;
115 | }
116 | border: {
117 | top: 1px solid lighten($gray, 13%);
118 | right: none;
119 | bottom: none;
120 | left: none;
121 | }
122 | }
123 |
124 | pre {
125 | padding: 30px;
126 | overflow-x: scroll;
127 | border-radius: 5px;
128 | color: $white;
129 | code {
130 | overflow-x: scroll;
131 | line-height: 1.33;
132 | word-break: normal;
133 | }
134 | }
135 |
136 | code {
137 | font: {
138 | weight: bold;
139 | family: "courier", monospace;
140 | }
141 | }
142 |
143 | ul, ol {
144 | margin-top: 18px;
145 | li {
146 | padding-left: 23px;
147 | text-indent: -23px;
148 | &:not(:first-child) {
149 | margin-top: 12px;
150 | }
151 | }
152 | }
153 |
154 | ul {
155 | li {
156 | &:before {
157 | @extend %bullet;
158 | }
159 | p {
160 | display: inline;
161 | }
162 | }
163 | }
164 |
165 | ol li {
166 | counter-increment: counter;
167 | &:before {
168 | margin-right: 5px;
169 | font-weight: 800;
170 | content: counter(counter, decimal) '. ';
171 | }
172 | }
173 |
174 | li {
175 | word-wrap: break-word;
176 | }
177 |
178 | img {
179 | margin-right: 12px;
180 | height: 32px;
181 | width: 32px;
182 | border: {
183 | radius: 16px;
184 | radius: 50%;
185 | }
186 | }
187 |
188 | table {
189 | width: 100%;
190 | border-collapse: collapse;
191 | }
192 |
193 | thead, tbody {
194 | vertical-align: top;
195 | }
196 |
197 | thead tr {
198 | border-bottom: 5px solid $gray;
199 | }
200 |
201 | th, td {
202 | padding-right: 16px;
203 | }
204 |
205 | th {
206 | padding-bottom: 16px;
207 | text: {
208 | align: left;
209 | transform: uppercase;
210 | }
211 | }
212 |
213 | tbody tr {
214 | border-bottom: 1px solid lighten($gray, 13%);
215 | }
216 |
217 | td {
218 | padding: {
219 | top: 16px;
220 | bottom: 28px;
221 | }
222 | code {
223 | font: {
224 | weight: normal;
225 | size: 13px;
226 | }
227 | }
228 | }
229 |
230 | blockquote {
231 | padding-left: 20px;
232 | border-left: 10px solid lighten($gray, 13%);
233 | p {
234 | font-style: italic;
235 | }
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/stylesheets/modules/_header.scss:
--------------------------------------------------------------------------------
1 | .l-wrap--header {
2 | @media (min-width: 1020px) {
3 | position: relative;
4 | max-width: 1100px;
5 | }
6 | @media (min-width: 1020px) and (max-width: 1199px) {
7 | margin: {
8 | right: 5%;
9 | left: 5%;
10 | }
11 | }
12 | @media (min-width: 1200px) {
13 | margin: {
14 | right: auto;
15 | left: auto;
16 | }
17 | }
18 | }
19 |
20 | .header {
21 | -webkit-transform: translateZ(0); // Weird fix to make transition smoother in Safari
22 | @media (max-width: 1019px) {
23 | height: 48px;
24 | }
25 | @media (min-width: 1020px) {
26 | height: 68px;
27 | }
28 | }
29 |
30 | .header--interior {
31 | border-bottom-style: solid;
32 | @media (max-width: 1019px) {
33 | border-bottom: {
34 | width: 1px;
35 | color: darken($red, 10%);
36 | }
37 | }
38 | @media (min-width: 1020px) {
39 | border-bottom: {
40 | width: 7px;
41 | color: $black;
42 | }
43 | box-shadow: inset 0 -2px 0 0 rgba($white, .3);
44 | }
45 | }
46 |
47 | .header__logo-wrap {
48 | position: relative;
49 | float: left;
50 | z-index: 1;
51 | &:focus {
52 | outline: none;
53 | }
54 | &:focus, &:hover {
55 | .header__logo:before {
56 | @include rotate(360deg);
57 | }
58 | }
59 | @media (max-width: 1019px) {
60 | top: 9px;
61 | left: 5%;
62 | font-size: 26px;
63 | }
64 | @media (min-width: 1020px) {
65 | top: 12px;
66 | font-size: 40px;
67 | }
68 | }
69 |
70 | .header__logo {
71 | position: absolute;
72 | font-family: "icomoon";
73 | @extend %font-smoothing;
74 | color: $white;
75 | speak: none;
76 | @include rotate(0deg);
77 | &:before {
78 | position: absolute;
79 | @extend %font-smoothing;
80 | @include transition-duration(.75s);
81 | @include transition-property(transform);
82 | }
83 | @media (max-width: 1019px) {
84 | line-height: 32px;
85 | }
86 | @media (min-width: 1020px) {
87 | line-height: 45px;
88 | }
89 | }
90 |
91 | .header__club-sandwich {
92 | &:before {
93 | @extend %font-smoothing;
94 | }
95 | @media (max-width: 1019px) {
96 | &:before {
97 | content: '≡';
98 | margin-right: 5%;
99 | padding: 14px;
100 | position: relative;
101 | right: -14px;
102 | float: right;
103 | font: {
104 | family: "icomoon";
105 | size: 20px;
106 | }
107 | speak: none;
108 | color: $white;
109 | @include transition-duration(.25s);
110 | @include transition-property(color);
111 | }
112 | &:hover:before, &:focus:before {
113 | color: rgba($white, .3);
114 | }
115 | }
116 | @media (min-width: 1020px) {
117 | display: none;
118 | }
119 | }
120 |
121 | .header__nav-links-wrap {
122 | position: absolute;
123 | @media (max-width: 1019px) {
124 | top: 0;
125 | right: -270px;
126 | width: 270px;
127 | }
128 | @media (min-width: 1020px) {
129 | width: 100%;
130 | }
131 | }
132 |
133 | .header__nav-links {
134 | @media (min-width: 1020px) {
135 | float: right;
136 | }
137 | }
138 |
139 | .header__nav-link {
140 | @extend %box-sizing;
141 | font-weight: 500;
142 | text-transform: uppercase;
143 | color: $white;
144 | @include transition-duration(.25s);
145 | &:focus {
146 | outline: none;
147 | }
148 | @media (max-width: 1019px) {
149 | padding: 18px 30px;
150 | display: block;
151 | width: 100%;
152 | border-bottom: 1px solid darken($red, 10%);
153 | @include transition-property(background-color, border-top-color);
154 | &:focus, &:hover {
155 | border-top-color: darken($red, 10%);
156 | background-color: darken($red, 10%);
157 | }
158 | &:active, &.is-active {
159 | background-color: $white;
160 | color: $red;
161 | }
162 | }
163 | @media (min-width: 1020px) {
164 | display: inline-block;
165 | padding: {
166 | top: 25px;
167 | bottom: 26px;
168 | }
169 | margin: {
170 | right: 16px;
171 | left: 16px;
172 | }
173 | border-bottom: 5px solid transparent;
174 | box-shadow: inset 0 5px 0 0 transparent;
175 | @include transition-property(box-shadow);
176 | &:last-child {
177 | margin-right: -16px;
178 | }
179 | &:focus, &:hover {
180 | box-shadow: inset 0 5px 0 0 $white;
181 | }
182 | &.is-active {
183 | position: relative;
184 | border-bottom-color: $red;
185 | &:after {
186 | content: "";
187 | position: absolute;
188 | bottom: -10px;
189 | @include calc(left, "50% - 5px", 50%);
190 | border: {
191 | width: 5px 5px 0;
192 | style: solid;
193 | color: $red transparent;
194 | }
195 | display: block;
196 | }
197 | }
198 | }
199 | span {
200 | display: inline-block;
201 | position: relative;
202 | max-width: 150px;
203 | overflow: hidden;
204 | text-overflow: ellipsis;
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/publishing.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Publishing your gem
4 | url: /publishing
5 | previous: /name-your-gem
6 | next: /security
7 | ---
8 |
9 | Start with an idea, end with a distributable package of Ruby code.
10 |
11 | Ways to share your gem code with other users.
12 |
13 | * [Introduction](#introduction)
14 | * [Sharing Source Code](#sharing-source-code)
15 | * [Serving Your Own Gems](#serving-your-own-gems)
16 | * [Publishing to RubyGems.org](#publishing-to-rubygemsorg)
17 | * [Push Permissions on RubyGems.org](#push-permissions-on-rubygemsorg)
18 | * [Gem Security](#gem-security)
19 |
20 | Introduction
21 | ------------
22 |
23 | Now that you've [created your gem](/make-your-own-gem), you're probably ready
24 | to share it. While it is perfectly reasonable to create private gems solely to
25 | organize the code in large private projects, it's more common to build gems so
26 | that they can be used by multiple projects. This guide discusses the various
27 | ways that you can share your gem with the world.
28 |
29 | Sharing Source Code
30 | -------------------
31 |
32 | The simplest way (from the author's perspective) to share a gem for other
33 | developers' use is to distribute it in source code form. If you place the full
34 | source code for your gem on a public git repository (often, though not always,
35 | this means sharing it via [GitHub](https://github.com)), then other users can
36 | install it with [Bundler's git functionality](https://bundler.io/v2.0/man/gemfile.5.html#GIT).
37 |
38 | For example, you can install the latest code for the wicked_pdf gem in a
39 | project by including this line in your Gemfile:
40 |
41 | gem "wicked_pdf", :git => "https://github.com/mileszs/wicked_pdf.git"
42 |
43 | > Installing a gem directly from a git repository is a feature of Bundler, not
44 | > a feature of RubyGems. Gems installed this way will not show up when you run
45 | > `gem list`.
46 |
47 | Serving Your Own Gems
48 | ---------------------
49 |
50 | If you want to control who can install a gem, or directly track the activity
51 | surrounding a gem, then you'll want to set up a private gem server. You can
52 | [set up your own gem server](/run-your-own-gem-server) or use a commercial
53 | service such as [Gemfury](http://www.gemfury.com/).
54 |
55 | RubyGems 2.2.0 and newer support the `allowed_push_host` metadata value to
56 | restrict gem pushes to a single host. If you are publishing private gems you
57 | should set this value to prevent accidental pushes to rubygems.org:
58 |
59 | Gem::Specification.new 'my_gem', '1.0' do |s|
60 | # ...
61 | s.metadata['allowed_push_host'] = 'https://gems.my-company.example'
62 | end
63 |
64 | See the [Resources](/resources) guide for an up-to-date listing of options for
65 | private gem servers.
66 |
67 | Publishing to RubyGems.org
68 | --------------------------
69 |
70 | The simplest way to distribute a gem for public consumption is to use
71 | [RubyGems.org](https://rubygems.org/). Gems that are published to RubyGems.org
72 | can be installed via the `gem install` command or through the use of tools such
73 | as Isolate or Bundler.
74 |
75 | To begin, you'll need to create an account on RubyGems.org. Visit the [sign
76 | up](https://rubygems.org/users/new) page and supply an email address that you
77 | control, a handle (username) and a password.
78 |
79 | After creating the account, use your email and password when pushing the gem.
80 | (RubyGems saves the credentials in ~/.gem/credentials for you so you only need
81 | to log in once.)
82 |
83 | Note that your gem name must be unique. It cannot have a name that is already
84 | in use by another gem already published to [RubyGems.org](https://rubygems.org/).
85 |
86 | To publish version 0.1.0 of a new gem named 'squid-utils':
87 |
88 | $ gem push squid-utils-0.1.0.gem
89 | Enter your RubyGems.org credentials.
90 | Don't have an account yet? Create one at https://rubygems.org/sign_up
91 | Email: gem_author@example
92 | Password:
93 | Signed in.
94 | Pushing gem to RubyGems.org...
95 | Successfully registered gem: squid-utils (0.1.0)
96 |
97 | Congratulations! Your new gem is now ready for any ruby user in the world to
98 | install!
99 |
100 | Push Permissions on RubyGems.org
101 | --------------------------------
102 |
103 | If you have multiple maintainers for your gem you can give your fellow
104 | maintainers permission to push the gem to rubygems.org through the [gem
105 | owner command](/command-reference/#gem-owner).
106 |
107 | "Access Denied" Error When Pushing to RubyGems.org
108 | --------------------------------------------------
109 |
110 | In certain situations, you may get this error:
111 |
112 | Pushing gem to https://rubygems.org...
113 | Access Denied. Please sign up for an account at https://rubygems.org
114 |
115 | If you encounter this error and aren't sure why, try running `gem signout`
116 | and then `gem signin`.
117 |
118 | There is [an open issue about improving how this is
119 | handled](https://github.com/rubygems/rubygems/issues/7595).
120 |
121 | Gem Security
122 | ------------
123 |
124 | See [Security](/security) page.
125 |
--------------------------------------------------------------------------------
/stylesheets/screen.scss:
--------------------------------------------------------------------------------
1 | @import 'flutie';
2 |
3 | body {
4 | font-family: helvetica neue, helvetica, sans-serif;
5 | line-height: 1;
6 | }
7 | body.home {
8 | background: url("/images/background.jpg") repeat;
9 | }
10 |
11 | .left {
12 | float: left;
13 | }
14 | .right {
15 | float: right;
16 | }
17 |
18 | #head-wrapper {
19 | background: #240101 url("/images/background.jpg") repeat;
20 | width: 100%;
21 |
22 | #small-top {
23 | margin: 0 auto;
24 | padding: 20px 0;
25 | max-width: 480px;
26 |
27 | header {
28 | margin: 0 auto;
29 | height: 75px;
30 | max-width: 450px;
31 |
32 | a {
33 | text-decoration: none;
34 | }
35 |
36 | h1 {
37 | background: url("/images/logo-small.png") 5px 0 no-repeat;
38 | float: left;
39 | height: 70px;
40 | padding-top: 13px;
41 | text-indent: 90px;
42 | color: white;
43 | font: 300 42px/42px "proxima-nova-1", "proxima-nova-2", "museo-sans-1", "museo-sans-2", helvetica neue, sans-serif;
44 | letter-spacing: 1px;
45 | text-shadow: 0 0 1px #333;
46 | }
47 | }
48 | }
49 |
50 | #homepage-top {
51 | margin: 0 auto;
52 | padding: 30px 0;
53 | width: 978px;
54 |
55 | header {
56 | height: 150px;
57 |
58 | h1 {
59 | background: url("/images/logo.png") 5px 0 no-repeat;
60 | float: left;
61 | height: 150px;
62 | text-indent: -9999px;
63 | width: 168px;
64 | }
65 |
66 | #the-sell {
67 | float: left;
68 | margin: 25px 0 0 0;
69 | width: 726px;
70 |
71 | h2 {
72 | color: white;
73 | font: 300 42px/42px helvetica neue, sans-serif;
74 | letter-spacing: 1px;
75 | text-shadow: 0 0 1px #333;
76 | }
77 |
78 | p.tagline {
79 | color: #CCC;
80 | font: 300 18px/22px helvetica neue, sans-serif;
81 | margin: 6px 0 0 0;
82 | text-shadow: 0 0 1px #333;
83 |
84 | a, a:visited, a:link {
85 | color: #ccc;
86 | }
87 | }
88 | }
89 | }
90 |
91 | #chapters {
92 | margin: 0 0 0 168px;
93 |
94 | a, a:visited, a:link {
95 | color: #fff;
96 | text-decoration: none;
97 | }
98 |
99 | a:hover {
100 | color: #AD141E;
101 | }
102 |
103 | h2 {
104 | color: white;
105 | font: 700 30px/32px "proxima-nova-1", "proxima-nova-2", "museo-sans-1", "museo-sans-2", helvetica neue, sans-serif;
106 | text-shadow: 0 1px 1px #222;
107 | margin-top: 20px;
108 | }
109 |
110 | p {
111 | color: #AAA;
112 | font: 300 16px/18px "proxima-nova-1", "proxima-nova-2", "museo-sans-1", "museo-sans-2", helvetica neue, sans-serif;
113 | margin: 4px 122px 0 0;
114 |
115 | a, a:visited, a:link {
116 | color: #AAA;
117 | }
118 |
119 | a:hover {
120 | color: #AD141E;
121 | }
122 |
123 | }
124 |
125 | p.introduction {
126 | margin-bottom: 1.5em;
127 | line-height: 1.3em;
128 | width: 70%;
129 | color: #CCC;
130 | }
131 | }
132 | }
133 | }
134 |
135 | #container {
136 | margin: 0 auto;
137 | max-width: 480px;
138 | padding: 0 7px;
139 |
140 | .nav {
141 | margin: 25px 0 0 0;
142 | height: 24px;
143 | text-transform: uppercase;
144 | font-size: 16px;
145 | width: 100%;
146 |
147 | a {
148 | color: #AD141E;
149 | text-decoration: none;
150 | }
151 | a:hover {
152 | text-decoration: underline;
153 | }
154 | }
155 |
156 | #content {
157 | font: 400 16px/24px helvetica neue, sans-serif;
158 |
159 | a {
160 | color: #AD141E;
161 | }
162 |
163 | h1 {
164 | font: 700 30px/50px helvetica neue, sans-serif;
165 | padding-top: 10px;
166 | a {
167 | color: #000;
168 | text-decoration: none;
169 | }
170 | }
171 |
172 | h2 {
173 | font-size: 18px;
174 | font-weight: bold;
175 | margin: 25px 0;
176 | clear: both;
177 | border-top: 1px solid #ddd;
178 | padding-top: 10px;
179 | }
180 |
181 | h3 {
182 | font-size: 14px;
183 | }
184 |
185 | p {
186 | margin: 1em 0;
187 | }
188 |
189 | pre {
190 | overflow: auto;
191 | background: #333;
192 | color: #fff;
193 | padding: 10px;
194 | font-family: Monaco, Consolas, "Courier New", Courier, Sans-serif;
195 |
196 | code {
197 | background: #333;
198 | }
199 | }
200 |
201 | code {
202 | background: #eee;
203 | padding: 2px;
204 | }
205 |
206 | tt {
207 | display: inline;
208 | }
209 |
210 | ul, ol {
211 | padding-top: 10px;
212 | margin-left: 30px;
213 | list-style: square;
214 |
215 | li {
216 | padding-bottom: 0px;
217 | }
218 | }
219 |
220 | ol {
221 | list-style: decimal;
222 | }
223 | }
224 |
225 | footer {
226 | margin: 50px 0;
227 | text-align: center;
228 | p {
229 | color: #666;
230 | font: 400 14px "proxima-nova-1", "proxima-nova-2", "museo-sans-1", "museo-sans-2", helvetica neue, sans-serif;
231 | a, a:visited, a:link {
232 | color: #666;
233 | }
234 | }
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/resources.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Resources
4 | url: /resources
5 | previous: /using-s3-source
6 | next: /contributing
7 | ---
8 |
9 | Great blog posts, tutorials, and other sites to help you out.
10 |
11 | A collection of helpful material about RubyGems. Feel free to
12 | [fork](https://github.com/rubygems/guides) and add your own!
13 |
14 | Tutorials
15 | ---------
16 |
17 | * [Making Ruby Gems](http://timelessrepo.com/making-ruby-gems)
18 | * [Gemcutter & Jeweler](http://railscasts.com/episodes/183-gemcutter-jeweler)
19 | * [MicroGems: five minute RubyGems](https://jeffkreeftmeijer.com/2011/microgems-five-minute-rubygems/) - Gems so small that you can store them in a gist.
20 | * [Let's Write a Gem: Part 1](http://rakeroutes.com/blog/lets-write-a-gem-part-one/) and [Part 2](http://rakeroutes.com/blog/lets-write-a-gem-part-two/)
21 | * [Polishing Rubies](https://wayback.archive.org/web/20151009055337/https://www.intridea.com/blog/tag/polishing%20rubies)
22 | * [A Practical Guide to Using Signed Ruby Gems - Part 1: Bundler](http://blog.meldium.com/home/2013/3/3/signed-rubygems-part)
23 | * [Basic RubyGem Development](http://tech.pro/tutorial/1226/basic-rubygem-development) and [Intermediate RubyGem Development](http://tech.pro/tutorial/1277/intermediate-rubygem-development)
24 | * [How to make a Rubygem](https://www.alexedwards.net/blog/how-to-make-a-rubygem) and [How to make a Rubygem: Part Two](https://www.alexedwards.net/blog/how-to-make-a-rubygem-part-two)
25 | * [Crafting Gems](http://railsconftutorials.com/2013/sessions/crafting_gems.html) - A tutorial from RailsConf 2013.
26 | * [How to cryptographically sign your RubyGem](https://www.benjaminfleischer.com/2013/11/08/how-to-sign-your-rubygem-cert/) - Step-by-step guide
27 |
28 | Presentations
29 | -------------
30 |
31 | * [History of RDoc and RubyGems](https://web.archive.org/web/20110527141407/https://blog.segment7.net/2011/01/17/history-of-rdoc-and-rubygems)
32 | * [Building a Gem](https://www.slideshare.net/sarah.allen/building-a-ruby-gem)
33 | * [Gemology](https://www.slideshare.net/copiousfreetime/gemology)
34 |
35 | Philosophy
36 | ----------
37 |
38 | * [Semantic Versioning](https://semver.org/)
39 | * [Ruby Packaging Standard](https://web.archive.org/web/20170704013540/https://chneukirchen.github.io/rps/)
40 | * [Why `require 'rubygems'` Is Wrong](https://tomayko.com/writings/require-rubygems-antipattern)
41 | * [How to Name Gems](https://web.archive.org/web/20130821183311/http://blog.segment7.net:80/2010/11/15/how-to-name-gems)
42 | * [Make the world a better place; put a license in your gemspec](https://www.benjaminfleischer.com/2013/07/12/put-a-license-in-your-gemspec/)
43 |
44 | Patterns
45 | --------
46 |
47 | * [Gem Packaging: Best Practices](https://weblog.rubyonrails.org/2009/9/1/gem-packaging-best-practices)
48 | * [Rubygems Good Practice](https://yehudakatz.com/2009/07/24/rubygems-good-practice/)
49 | * [Gem Development Best Practices](https://blog.carbonfive.com/2011/01/22/gem-development-best-practices/)
50 |
51 | Creating
52 | --------
53 |
54 | Tools to help build gems.
55 |
56 | * [gemerator](https://github.com/rkh/gemerator) - Minimalist tool for generating skeleton gems.
57 | * [hoe](https://github.com/seattlerb/hoe) - Rake/RubyGems helper.
58 | * [Jeweler](https://github.com/technicalpickles/jeweler) - Opinionated tool for managing RubyGems projects.
59 | * [micro-cutter](https://github.com/tjh/micro-cutter) - Tool to build the base files for a MicroGem.
60 | * [newgem](https://github.com/drnic/newgem) - New gem generator.
61 | * [RStack](https://github.com/jrun/rstack) - Generator intended for use on private gems.
62 | * [rubygems-tasks](https://github.com/postmodern/rubygems-tasks) - Rake tasks for building, installing, and releasing Ruby Gems.
63 | * [ore](https://github.com/ruby-ore/ore) - Project generator with a variety of templates.
64 | * [Omnibus](https://github.com/opscode/omnibus-ruby) - Generate full-stack installers for ruby code (see this [Omnibus tutorial](https://blog.scoutapp.com/articles/2013/06/21/omnibus-tutorial-package-a-standalone-ruby-gem) for instructions on using it to package a standalone RubyGem.)
65 |
66 | Monitoring
67 | ----------
68 |
69 | Tools to watch gems for changes.
70 |
71 | * [Depfu](https://depfu.com/) - Depfu continuously updates your dependencies one gem at a time and creates a pull request with all the info you need. Free for open source.
72 | * [Gemnasium](https://gemnasium.com/) - Parses your GitHub projects to learn what to notify you about. Free for public repos only.
73 | * [Gemnasium gem](https://github.com/gemnasium/gemnasium-gem) - Allows you to use Gemnasium without granting it access to private repos.
74 | * [gemwhisperer](https://github.com/rubygems/gemwhisperer)
75 | * [Libraries.io](https://libraries.io/) - Get alerts for new versions of the gems you depend upon.
76 |
77 | Hosting and Serving
78 | -------------------
79 |
80 | * [Geminabox](https://github.com/cwninja/geminabox) - Host your own gems, with a rubygems-compatible API.
81 | * [Gem Mirror](https://github.com/YorickPeterse/gem-mirror) - Run an internal mirror of external gem sources.
82 | * [Gemfury](http://www.gemfury.com/) - Private cloud-based RubyGems servers. Priced by number of collaborators.
83 |
84 | Utilities
85 | ---------
86 |
87 | * [gemnasium-parser](https://github.com/laserlemon/gemnasium-parser) - Determine dependencies without evaluating the ruby in gemfiles or gemspecs
88 | * [Gemrat](https://github.com/DruRly/gemrat) - Add the latest version of a gem to your Gemfile from the command line.
89 |
--------------------------------------------------------------------------------
/releasing-rubygems.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Releasing Rubygems
4 | url: /releasing-rubygems
5 | previous: /cve
6 | next: /trusted-publishing
7 | ---
8 |
9 | A runbook for making RubyGems releases.
10 |
11 | RubyGems has a more complicated release process than most gems do.
12 | RubyGems updates are shipped in a [wrapper gem](https://rubygems.org/gems/rubygems-update)
13 | that the `gem update --system` command downloads, and then
14 | runs [`setup.rb`](https://github.com/rubygems/rubygems/blob/master/setup.rb).
15 |
16 | RubyGems adheres to [semantic versioning](https://semver.org/) in its version numbering.
17 |
18 | _Note: In the documentation listed below, the *current* minor version number is
19 | 2.7 and the *next* minor version number is 2.8_
20 |
21 | Regardless of the version, *all releases* must update the `History.txt` and `lib/rubygems.rb`
22 | files. The changelog for the first stable minor release (`2.7.0`) is a sum of all
23 | the preceding pre-release versions (`2.7.pre.1`, `1.12.pre.2`, etc) for that
24 | minor version. The changelog for the first stable minor release is left blank
25 | unless there are fixes included since the last pre/rc release.
26 |
27 | ## Workflow
28 |
29 | In general, `master` will accept PRs for:
30 |
31 | * feature merges for the next minor version (2.8)
32 | * regression fix merges for a patch release on the current minor version (2.7)
33 |
34 | ### Breaking releases
35 |
36 | RubyGems cares a lot about preserving compatibility. As a result, changes that
37 | break backwards compatibility should (whenever this is possible) include a feature
38 | release that is backwards compatible, and issue warnings for all options and
39 | behaviors that will change.
40 |
41 | We try very hard to only release breaking changes when incrementing the _major_
42 | version of RubyGems.
43 |
44 | ### Cherry picking
45 |
46 | Patch releases are made by cherry-picking bug fixes from `master`.
47 |
48 | When we cherry-pick, we cherry-pick the merge commits using the following command:
49 |
50 | ```bash
51 | $ git cherry-pick -m 1 MERGE_COMMIT_SHAS
52 | ```
53 |
54 | The `./util/patch_with_prs.rb` utility will automatically handle
55 | cherry-picking, and is further detailed below.
56 |
57 | ## History
58 |
59 | RubyGems maintains a list of changes present in each version in the `History.txt` file.
60 | Entries are added immediately before making a release by using the
61 | `./util/update_changelog.rb` utility.
62 | Generally, each PR that's included in the release will get an entry.
63 |
64 | ## Releases
65 |
66 | ### Minor releases
67 |
68 | While pushing a gem version to RubyGems.org is as simple as `rake release`,
69 | releasing a new version of RubyGems includes a lot of communication: team consensus,
70 | git branching, changelog writing, documentation site updates, and a blog post.
71 |
72 | Dizzy yet? Us too.
73 |
74 | Here's the checklist for releasing new minor versions:
75 |
76 | * [ ] Check with the core team to ensure that there is consensus around shipping a
77 | feature release. As a general rule, this should always be okay, since features
78 | should _never break backwards compatibility_
79 | * [ ] Create a new stable branch from master (see **Branching** below)
80 | * [ ] Update the `VERSION` constant in `lib/rubygems.rb` to the new version number
81 | * [ ] Update `History.txt` to include all of the changes in the release
82 | * [ ] Run `rake release`, tweet, blog, let people know about the prerelease!
83 |
84 | At this point, you're a release manager! Pour yourself several tasty drinks and
85 | think about taking a vacation in the tropics.
86 |
87 | Beware, the first couple of days after the first version in a minor version
88 | series can often yield a lot of bug reports. This is normal, and doesn't mean you've done
89 | _anything_ wrong as the release manager.
90 |
91 | #### Branching
92 |
93 | Minor releases of the next version start with a new release branch from the
94 | current state of master: `2.7`.
95 |
96 | Once that stable branch has been cut from `master`, changes for that minor
97 | release series (2.7) will only be made _intentionally_, via patch releases.
98 | That is to say, changes to `master` by default _won't_ make their way into any
99 | `2.7` version, and development on `master` will be targeting the next minor
100 | or major release.
101 |
102 | ### Patch releases (bug fixes!)
103 |
104 | Releasing new bugfix versions is really straightforward. Increment the tiny version
105 | number in `lib/rubygems.rb`, and in `History.txt` add one bullet point
106 | per bug fixed. Then run `rake release` from the `2.7` (stable) branch,
107 | and pour yourself a tasty drink!
108 |
109 | PRs containing regression fixes for a patch release of the current minor version
110 | are merged to master. These commits are then cherry-picked from master onto the
111 | minor branch (`2.7`).
112 |
113 | There is a `./util/patch_with_prs.rb` utility that automates creating a patch release.
114 | It takes a single option, the _exact_ patch release being made (e.g. `--version=2.7.8`),
115 | and all other arguments are the PR numbers to be included in the patch release.
116 | The utility checks out the appropriate stable branch (`2.7`), and then cherry-picks those changes
117 | (and only those changes) to the stable branch. The task then bumps the version in the
118 | version file, prompts you to update the `History.txt`, then will commit those changes
119 | and run `rake release`!
120 |
--------------------------------------------------------------------------------
/cve.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: RubyGems Common Vulnerabilities and Exposures
4 | url: /cve
5 | previous: /credits
6 | next: /releasing-rubygems
7 | ---
8 |
9 | # RubyGems Common Vulnerabilities and Exposures
10 |
11 | ## CVE-2013-4287: Algorithmic complexity vulnerability in RubyGems 2.0.7 and older
12 |
13 | RubyGems validates versions with a regular expression that is vulnerable to
14 | denial of service due to backtracking. For specially crafted RubyGems
15 | versions attackers can cause denial of service through CPU consumption.
16 |
17 | RubyGems versions 2.0.7 and older, 2.1.0.rc.1 and 2.1.0.rc.2 are vulnerable.
18 |
19 | Ruby versions 1.9.0 through 2.0.0p247 are vulnerable as they contain embedded
20 | versions of RubyGems.
21 |
22 | It does not appear to be possible to exploit this vulnerability by installing a
23 | gem for RubyGems 1.8.x or 2.0.x. Vulnerable uses of RubyGems API include
24 | packaging a gem (through `gem build`, Gem::Package or Gem::PackageTask),
25 | sending user input to Gem::Version.new, Gem::Version.correct? or use of the
26 | Gem::Version::VERSION_PATTERN or Gem::Version::ANCHORED_VERSION_PATTERN
27 | constants.
28 |
29 | Notably, users of bundler that install gems from git are vulnerable if a
30 | malicious author changes the gemspec to an invalid version.
31 |
32 | The vulnerability can be fixed by changing the first grouping to an atomic
33 | grouping in Gem::Version::VERSION_PATTERN in lib/rubygems/version.rb. For
34 | RubyGems 2.0.x:
35 |
36 | - VERSION_PATTERN = '[0-9]+(\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' # :nodoc:
37 | + VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' # :nodoc:
38 |
39 | For RubyGems 1.8.x:
40 |
41 | - VERSION_PATTERN = '[0-9]+(\.[0-9a-zA-Z]+)*' # :nodoc:
42 | + VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*' # :nodoc:
43 |
44 | This vulnerability was discovered by Damir Sharipov
45 |
46 | ## CVE-2013-4363: Algorithmic complexity vulnerability in RubyGems 2.1.4 and older
47 |
48 | The patch for CVE-2013-4287 was insufficiently verified so the combined
49 | regular expression for verifying gem version remains vulnerable following
50 | CVE-2013-4287.
51 |
52 | RubyGems validates versions with a regular expression that is vulnerable to
53 | denial of service due to backtracking. For specially crafted RubyGems
54 | versions attackers can cause denial of service through CPU consumption.
55 |
56 | RubyGems versions 2.1.4 and older are vulnerable.
57 |
58 | Ruby versions 1.9.0 through 2.0.0p247 are vulnerable as they contain embedded
59 | versions of RubyGems.
60 |
61 | It does not appear to be possible to exploit this vulnerability by installing a
62 | gem for RubyGems 1.8.x or newer. Vulnerable uses of RubyGems API include
63 | packaging a gem (through `gem build`, Gem::Package or Gem::PackageTask),
64 | sending user input to Gem::Version.new, Gem::Version.correct? or use of the
65 | Gem::Version::VERSION_PATTERN or Gem::Version::ANCHORED_VERSION_PATTERN
66 | constants.
67 |
68 | Notably, users of bundler that install gems from git are vulnerable if a
69 | malicious author changes the gemspec to an invalid version.
70 |
71 | The vulnerability can be fixed by changing the "*" repetition to a "?"
72 | repetition in Gem::Version::ANCHORED_VERSION_PATTERN in
73 | lib/rubygems/version.rb. For RubyGems 2.1.x:
74 |
75 | - ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc:
76 | + ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ # :nodoc:
77 |
78 | For RubyGems 2.0.x:
79 |
80 | - ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc:
81 | + ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ # :nodoc:
82 |
83 | For RubyGems 1.8.x:
84 |
85 | - ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc:
86 | + ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ # :nodoc:
87 |
88 |
89 | This vulnerability was discovered by Alexander Cherepanov
90 |
91 | ## CVE-2015-3900: Request hijacking vulnerability in RubyGems 2.4.6 and earlier
92 |
93 | RubyGems provides the ability of a domain to direct clients to a separate
94 | host that is used to fetch gems and make API calls against. This mechanism
95 | is implemented via DNS, specifically a SRV record _rubygems._tcp under the
96 | original requested domain.
97 |
98 | For example, this is the one that users who use rubygems.org see:
99 |
100 | > dig _rubygems._tcp.rubygems.org SRV
101 |
102 | ;; ANSWER SECTION:
103 | _rubygems._tcp.rubygems.org. 600 IN SRV 0 1 80 api.rubygems.org.
104 |
105 | RubyGems did not validate the hostname returned in the SRV record before
106 | sending requests to it.
107 |
108 | This left clients open to a DNS hijack attack, whereby an attacker could
109 | return a SRV of their choosing and get the client to use it. For example:
110 |
111 | > dig _rubygems._tcp.rubygems.org SRV
112 |
113 | ;; ANSWER SECTION:
114 | _rubygems._tcp.rubygems.org. 600 IN SRV 0 1 80 gems.nottobetrusted.wtf
115 |
116 | The fix, detailed at https://github.com/rubygems/rubygems/commit/6bbee35,
117 | shows that we validate the record now to be under the original domain. This
118 | restricts the client to be using the original trust/security domain as they
119 | would have otherwise.
120 |
121 | RubyGems versions between 2.0 and 2.4.6 are vulnerable.
122 |
123 | RubyGems version 2.0.16, 2.2.4, and 2.4.7 have been released that fix this
124 | issue.
125 |
126 | Ruby versions 1.9.0 through 2.2.0 are vulnerable as they contain embedded
127 | versions of RubyGems.
128 |
129 | This vulnerability was reported by Jonathan Claudius .
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/stylesheets/application.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sourceRoot":"","sources":["_load.scss","_base.scss","_layout.scss","_type.scss","modules/_footer.scss","modules/_header.scss","modules/_projects.scss","modules/_search.scss","modules/_shared.scss","modules/_img.scss","modules/_beta-banner.scss","modules/nav/_nav--paginated.scss","modules/nav/_nav--v.scss"],"names":[],"mappings":";AASA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,kBAbK;;;AAgBP;EACE;EACA;EAEE;EACA,qBArBG;;;AAyBP;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;ACtCF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAaE;EACA;EACA;;;AAGF;AAAA;EAEE;;;AAGF;EACE,kBDxBI;EC0BF;EACA;;;AAWF;EACE;;;AAKF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA,kBD7DM;;AC8DN;EAEE,ODnEI;;;ACuER;EAEI;EACA;EACA;EAEF;EACA;;AAGF;EACE;EACA;EAEA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;ACzGF;EAEI;EACA;EAEF;;;AAGF;EACE;;;AAGF;EAEE;;;AAKA;EAFF;IAGI;IACA;;;;AAKF;EADF;IAEI;IACA;;;;AAKF;EADF;IAEI;;;AAEF;EAJF;IAKI;;;;AAMF;EAFF;IAGI;IACA;;;;AAKF;EADF;IAEI;;;AAEF;EAJF;IAKI;;;;AAMF;EAFF;IAGI;;;;AAKF;EADF;IAEI;;;;AAMF;EAFF;IAGI;;;;AC1EJ;EACE;;AACA;EAFF;IAGI;;;AAEF;EALF;IAMI;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAOF;EACE,OHhCI;EAiDJ,6BGhB6B;EHiB7B,0BGjB6B;EHkB7B,yBGlB6B;EHmB7B,wBGnB6B;EHoB7B,qBGpB6B;EHwB7B,6BGvBA;EHwBA,0BGxBA;EHyBA,yBGzBA;EH0BA,wBG1BA;EH2BA,qBG3BA;;AACA;EACE;;AAEF;EACE;;;AASF;EAEI;EACA;EAEF;;AAGF;EACE;;AAGF;EAEE;;AAGF;EACE;;AAGF;EACE;EAEE;EACA;EAEF;EACA;;AACA;EACE;;AAIJ;EAEI;EACA;EAEF;EAEE;EACA,cHrFC;;AGuFH;EACE;;AAEF;EACE;;AAIJ;EACE;EAEE;EACA;EACA;EAEF;;AAGF;EAEI;EACA;EAGA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA,OH3HI;;AG4HJ;EACE;EACA;EACA;;AAIJ;EAEI;EACA;;AAIJ;EACE;;AACA;EACE;EACA;;AACA;EACE;;AAUF;EACE;;AAKN;EACE;;AACA;EACE;EACA;EACA;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EAEE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EAEE;EACA;;AAIJ;EACE;;AAGF;EAEI;EACA;;AAEF;EAEI;EACA;;AAKN;EACE;EACA;;AACA;EACE;;;ACzON;EAEI;EACA;EAEF;EACA;EACA;EACA,kBJDM;EIEN;EACA;EACA;;AACA;EAZF;IAaI;;;;AAIJ;EAEI;EACA;EAEF;;AACA;EANF;IAOI;;;AAEF;EATF;IAUI;;;;AAIJ;EACE;EACA;EACA;EACA;;AACA;EALF;IAMI;;;AAEF;EARF;IASI;;;AAEF;EAXF;IAYI;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EAEE;EACA;EAEF;EAEE;EACA;EACA;EAEF;EACA,OJpEM;EIqEN;EJxBA,6BIyB6B;EJxB7B,0BIwB6B;EJvB7B,yBIuB6B;EJtB7B,wBIsB6B;EJrB7B,qBIqB6B;EJjB7B,6BIkBA;EJjBA,0BIiBA;EJhBA,yBIgBA;EJfA,wBIeA;EJdA,qBIcA;;AACA;EACE;;AAEF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;ACzHA;EADF;IAEI;IACA;;;AAEF;EALF;IAOM;IACA;;;AAGJ;EAXF;IAaM;IACA;;;;AAKN;EACE;;AACA;EAFF;IAGI;;;AAEF;EALF;IAMI;;;;AAIJ;EACE;;AACA;EAFF;IAIM;IACA;;;AAGJ;EARF;IAUM;IACA,qBLjCE;IKmCJ;;;;AAIJ;EACE;EACA;EACA;;AACA;EACE;;AAGA;ELkBF;EACA;EACA;;AKhBA;EAZF;IAaI;IACA;IACA;;;AAEF;EAjBF;IAkBI;IACA;;;;AAIJ;EACE;EACA;EAEA,OLrEM;EKsEN;ELFA;EACA;EACA;;AKEA;EACE;EL5BF,6BK8B+B;EL7B/B,0BK6B+B;EL5B/B,yBK4B+B;EL3B/B,wBK2B+B;EL1B/B,qBK0B+B;ELtB/B,6BKuBE;ELtBF,0BKsBE;ELrBF,yBKqBE;ELpBF,wBKoBE;ELnBF,qBKmBE;;AAEF;EAbF;IAcI;;;AAEF;EAhBF;IAiBI;;;;AAQF;EACE;IACE;IACA;IACA;IACA;IACA;IACA;IAEE;IACA;IAEF;IACA,OLvGE;IA6CN,6BK2DiC;IL1DjC,0BK0DiC;ILzDjC,yBKyDiC;ILxDjC,wBKwDiC;ILvDjC,qBKuDiC;ILnDjC,6BKoDI;ILnDJ,0BKmDI;ILlDJ,yBKkDI;ILjDJ,wBKiDI;ILhDJ,qBKgDI;;EAEF;IACE;;;AAGJ;EAzBF;IA0BI;;;;AAIJ;EACE;;AACA;EAFF;IAGI;IACA;IACA;;;AAEF;EAPF;IAQI;;;;AAKF;EADF;IAEI;;;;AAIJ;EAEE;EACA;EACA,OL1IM;EA6CN,6BK8F6B;EL7F7B,0BK6F6B;EL5F7B,yBK4F6B;EL3F7B,wBK2F6B;EL1F7B,qBK0F6B;;AAC7B;EACE;;AAEF;EATF;IAUI;IACA;IACA;IACA;IL9FF,6BK+FE;IL9FF,0BK8FE;IL7FF,yBK6FE;IL5FF,wBK4FE;IL3FF,qBK2FE;;EACA;IACE;IACA;;EAEF;IACE,kBL1JE;IK2JF,OL/JA;;;AKkKJ;EAxBF;IAyBI;IAEE;IACA;IAGA;IACA;IAEF;IACA;ILpHF,6BKqHE;ILpHF,0BKoHE;ILnHF,yBKmHE;ILlHF,wBKkHE;ILjHF,qBKiHE;;EACA;IACE;;EAEF;IACE;;EAEF;IACE;IACA,qBLvLA;;EKwLA;IACE;IACA;IACA;ILjJN,MKkJuC;ILjJvC;IACA;IACA;IKiJQ;IACA;IACA;IAEF;;;AAIN;EACE;EACA;EACA;EACA;EACA;;;AC3MJ;EAEI;EACA;;AAEF;EACE;EACA;EACA;EACA;;;AAIJ;EAEI;EACA;;;AAIJ;EAEI;EACA;EAEF;;AAEE;EAEE;;;AC7BN;EACE;EACA;;;AAGF;EACE;EACA;EACA,kBPJM;EOKN;EACA;EAEE;EACA;EAEF,OPRM;EOSN;;AACA;EACE,OPlBE;;AOoBJ;EACE,OPrBE;;AOuBJ;EACE,OPxBE;;AO0BJ;EACE,OP3BE;;;AO+BN;EACE;;AAEA;EAHF;IAII;IAEE;IACA;IACA;;EAEF;IAVJ;MAWM;MACA;;;;AAGJ;EAfF;IAiBM;IACA;IACA;IAEF;;;AAEF;EAvBF;IAwBI;;;AAEF;EA1BF;IA2BI;;;;AAMF;EAFF;IAGI;;;;AAIJ;EAEE;EACA;EACA;EACA;EAEE;EACA;EAEF,OP/EI;EAiDJ,6BO+B6B;EP9B7B,0BO8B6B;EP7B7B,yBO6B6B;EP5B7B,wBO4B6B;EP3B7B,qBO2B6B;EPvB7B,6BOwBA;EPvBA,0BOuBA;EPtBA,yBOsBA;EPrBA,wBOqBA;EPpBA,qBOoBA;;AACA;EAbF;IAcI;;;AAEF;EACE;EACA;;;AAIJ;EAGE;EACA;EACA;EACA;;AACA;EACE;EACA,OPpGE;;AOsGJ;EACE;EACA,OPxGE;;AO0GJ;EACE;EACA,OP5GE;;AO8GJ;EACE;EACA,OPhHE;;AOmHF;EACE,OPpHA;;;AQCJ;EADF;IAEI;IACA;IACA;IR6CF,6BQ5C+B;IR6C/B,0BQ7C+B;IR8C/B,yBQ9C+B;IR+C/B,wBQ/C+B;IRgD/B,qBQhD+B;IRoD/B,6BQnDE;IRoDF,0BQpDE;IRqDF,yBQrDE;IRsDF,wBQtDE;IRuDF,qBQvDE;;EACA;IACE;;;;AAMJ;EACE;IACE;;;;AAKN;EACE;;;AAIA;EACE;IACE;;;;AAKN;EACE,kBR9BM;;AQ+BN;EAFF;IAGI;;;AAEF;EALF;IAMI;;;;AAIJ;EACE;EACA;;AACA;EAHF;IAKM;IACA;;;AAGJ;EATF;IAWM;IACA;;;;AAKN;EACE;EACA,ORvDM;EA0CN,6BQc6B;ERb7B,0BQa6B;ERZ7B,yBQY6B;ERX7B,wBQW6B;ERV7B,qBQU6B;ERN7B,6BQOA;ERNA,0BQMA;ERLA,yBQKA;ERJA,wBQIA;ERHA,qBQGA;;AACA;EACE,ORlEE;;AQoEJ;EACE;;;AAKF;EADF;IAEI;;EACA;IACE;;;;AAKN;EACE;;;ACnFF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;ACPF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;;;AAKJ;EACE;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;;;AC1DJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EXwCA,6BWvC6B;EXwC7B,0BWxC6B;EXyC7B,yBWzC6B;EX0C7B,wBW1C6B;EX2C7B,qBW3C6B;EX+C7B,6BW9CA;EX+CA,0BW/CA;EXgDA,yBWhDA;EXiDA,wBWjDA;EXkDA,qBWlDA;;AACA;EACE,OXNI;;AWQN;EACE;;AAEF;EACE;EACA;EACA;;;AAIJ;EACE;;AACA;EACE;;;AAIJ;EACE;EACA;EACA;;AACA;EACE;;AAEF;EACE;EACA;;AAEF;EACE;;;AC5CJ;EACE;EAEE;EACA;EAEF;EACA;EZ0CA,6BYzC6B;EZ0C7B,0BY1C6B;EZ2C7B,yBY3C6B;EZ4C7B,wBY5C6B;EZ6C7B,qBY7C6B;EZiD7B,6BYhDA;EZiDA,0BYjDA;EZkDA,yBYlDA;EZmDA,wBYnDA;EZoDA,qBYpDA;;AACA;EACE;;AAEF;EACE,OZdE;;;AYmBJ;EACE;;AAGF;EAIE;;AAHA;EACE;;;AAMN;EACE;EACA,OZ1BM;;AY2BN;EACE;IACE;;;AAGJ;EACE,OZxCE;;AY0CJ;EACE;;;AAIJ;EACE,OZ5CM;;AY6CN;EACE;;AAEF;EACE;;AAEF;EACE;IACE","file":"application.css"}
--------------------------------------------------------------------------------
/faqs.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Frequently Asked Questions
4 | url: /faqs
5 | previous: /contributing
6 | next: /plugins
7 | ---
8 |
9 | More of the "why" and "wtf" than "how".
10 |
11 | The RubyGems development team has gotten a lot of support requests over the
12 | years, and this is a list of the questions users both new and old that
13 | frequently pop up.
14 |
15 | * [I installed gems with `--user-install` and their commands are not available](#i-installed-gems-with---user-install-and-their-commands-are-not-available)
16 | * [How can I trust Gem code that's automatically downloaded?](#how-can-i-trust-gem-code-thats-automatically-downloaded)
17 | * [Why does `require 'some-gem'` fail?](#why-does-require-some-gem-fail)
18 | * [Why does require return false when loading a file from a gem?](#why-does-require-return-false-when-loading-a-file-from-a-gem)
19 |
20 | We also answer questions on [Bundler.io Slack][slack].
21 | Some of the information you can find on the support site includes:
22 |
23 | * [Installing gems with no network](https://help.rubygems.org/kb/rubygems/installing-gems-with-no-network)
24 | * [Why do I get HTTP Response 302 or 301 when installing a gem?](https://help.rubygems.org/kb/rubygems/why-do-i-get-http-response-302-or-301-when-installing-a-gem)
25 | * [RubyGems Upgrade Issues](https://help.rubygems.org/kb/rubygems/rubygems-upgrade-issues)
26 |
27 | [slack]: https://slack.bundler.io/
28 |
29 | I installed gems with `--user-install` and their commands are not available
30 | ---------------------------------------------------------------------------
31 |
32 | When you use the `--user-install` option, RubyGems will install the gems to a
33 | directory inside your home directory, something like `~/.gem/ruby/1.9.1`. The
34 | commands provided by the gems you installed will end up in
35 | `~/.gem/ruby/1.9.1/bin`. For the programs installed there to be available for
36 | you, you need to add `~/.gem/ruby/1.9.1/bin` to your `PATH` environment
37 | variable.
38 |
39 | For example, if you use bash you can add that directory to your `PATH` by
40 | adding code like this to your `~/.bashrc` file:
41 |
42 | if which ruby >/dev/null && which gem >/dev/null; then
43 | PATH="$(ruby -r rubygems -e 'puts Gem.user_dir')/bin:$PATH"
44 | fi
45 |
46 | After adding this code to your `~/.bashrc`, you need to restart your shell for
47 | the changes to take effect. You can do this by opening a new terminal window or
48 | by running `exec $SHELL` in the window you already have open.
49 |
50 | How can I trust Gem code that's automatically downloaded?
51 | ---------------------------------------------------------
52 |
53 | The same way you can trust any other code you install from the net: ultimately,
54 | you can't. You are responsible for knowing the source of the gems that you are
55 | using. In a setting where security is critical, you should only use known-good
56 | gems, and possibly perform your own security audit on the gem code.
57 |
58 | The Ruby community is discussing ways to make gem code more secure in the future,
59 | using some public-key infrastructure. To see the progress of this discussion, visit the
60 | [rubygems-trust](https://github.com/rubygems-trust) organization on GitHub.
61 |
62 | Why does `require 'some-gem'` fail?
63 | -----------------------------------
64 |
65 | Not every library has a strict mapping between the name of the gem and the name of
66 | the file you need to require. First you should check to see if the files match correctly:
67 |
68 | $ gem list RedCloth
69 |
70 | *** LOCAL GEMS ***
71 |
72 | RedCloth (4.1.1)
73 | $ ruby -e 'require "RedCloth"'
74 | /Library/Ruby/Site/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- RedCloth (LoadError)
75 | from /Library/Ruby/Site/1.8/rubygems/custom_require.rb:31:in `require'
76 | from -e:1
77 | $ gem contents --no-prefix RedCloth | grep lib
78 | lib/case_sensitive_require/RedCloth.rb
79 | lib/redcloth/erb_extension.rb
80 | lib/redcloth/formatters/base.rb
81 | lib/redcloth/formatters/html.rb
82 | lib/redcloth/formatters/latex.rb
83 | lib/redcloth/formatters/latex_entities.yml
84 | lib/redcloth/textile_doc.rb
85 | lib/redcloth/version.rb
86 | lib/redcloth.rb
87 | $ ruby -e 'require "redcloth"'
88 | $ # success!
89 |
90 | If you’re requiring the correct file, maybe `gem` is using a different ruby than `ruby`:
91 |
92 | $ which ruby
93 | /usr/local/bin/ruby
94 | $ gem env | grep 'RUBY EXECUTABLE'
95 | - RUBY EXECUTABLE: /usr/local/bin/ruby1.9
96 |
97 | In this instance we’ve got two ruby installations so that `gem` uses a different version than `ruby`. You can probably fix this by adjusting a symlink:
98 |
99 | $ ls -l /usr/local/bin/ruby*
100 | lrwxr-xr-x 1 root wheel 76 Jan 20 2010 /usr/local/bin/ruby@ -> /usr/local/bin/ruby1.8
101 | -rwxr-xr-x 1 root wheel 1213160 Jul 15 16:36 /usr/local/bin/ruby1.8*
102 | -rwxr-xr-x 1 root wheel 2698624 Jul 6 19:30 /usr/local/bin/ruby1.9*
103 | $ ls -l /usr/local/bin/gem*
104 | lrwxr-xr-x 1 root wheel 76 Jan 20 2010 /usr/local/bin/gem@ -> /usr/local/bin/gem1.9
105 | -rwxr-xr-x 1 root wheel 550 Jul 15 16:36 /usr/local/bin/gem1.8*
106 | -rwxr-xr-x 1 root wheel 550 Jul 6 19:30 /usr/local/bin/gem1.9*
107 |
108 | You may also need to give `irb` the same treatment.
109 |
110 | Why does require return false when loading a file from a gem?
111 | -------------------------------------------------------------
112 |
113 | Require returns false when loading a file from a gem. Usually require will return
114 | true when it has loaded correctly. What’s wrong?
115 |
116 | Nothing's wrong. Well, something. But nothing you need to worry about.
117 |
118 | A false return from the require method does not indicate an error. It just
119 | means that the file has already been loaded.
120 |
121 | RubyGems has a feature that allows a file to be automatically loaded
122 | when a gem is activated (i.e. selected). When you require a file that is
123 | in an inactive gem, the RubyGems software will activate that gem for you.
124 | During that activation, any autoloaded files will be loaded for you.
125 |
126 | So, by the time your require statement actually does the work of loading
127 | the file, it has already been autoloaded via the gem activation, and
128 | therefore the statement returns false.
129 |
--------------------------------------------------------------------------------
/run-your-own-gem-server.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Run your own gem server
4 | url: /run-your-own-gem-server
5 | previous: /api-key-scopes
6 | next: /setting-up-multifactor-authentication
7 | ---
8 |
9 | Need to serve gems locally or for your organization?
10 |
11 | There are times you would like to run your own gem server. You may want to
12 | share gems with colleagues when you are both without internet connectivity. You
13 | may have private code, internal to your organization, that you'd like to
14 | distribute and manage as gems without making the source publicly available.
15 |
16 | There are a few options to set up a server to host gems from within your
17 | organization. This guide covers the [Gemstash](https://github.com/rubygems/gemstash),
18 | [Gem in a Box](https://github.com/geminabox/geminabox), and
19 | [Gemirro](https://github.com/PierreRambaud/gemirro) projects. It also discusses
20 | how to use these servers as gem sources during development.
21 |
22 | ## Running Gemstash
23 |
24 | Gemstash is both a cache for remote servers (such as ),
25 | and a private gem source.
26 |
27 | To get started, install `gemstash`:
28 |
29 | $ gem install gemstash
30 |
31 | After it is installed, start the Gemstash server with the following command:
32 |
33 | $ gemstash start
34 |
35 | By default, the server runs on port 9292.
36 |
37 | If you want to use it as a cache, you can tell Bundler to use Gemstash to
38 | find gems from RubyGems.org:
39 |
40 | $ bundle config mirror.https://rubygems.org http://localhost:9292
41 |
42 | With this configuration, all gems fetched from RubyGems.org via bundler are
43 | cached by Gemstash.
44 |
45 | You can also push your own gems and use the gemstash server as a private
46 | gemsource. For more information about gemstash features and commands, read
47 | the [Gemstash](https://github.com/rubygems/gemstash) documentation.
48 |
49 | ## Running Gem in a Box
50 |
51 | For a server with more features, including the ability to push gems, try out
52 | the [Gem in a Box](https://github.com/geminabox/geminabox) project.
53 |
54 | To get started, install `geminabox`:
55 |
56 | [~/dev/geminabox] gem install geminabox
57 |
58 | Make a data directory for storing gems:
59 |
60 | [~/dev/geminabox] mkdir data
61 |
62 | Include the following in a `config.ru` file:
63 |
64 | [~/dev/geminabox] cat config.ru
65 | require "rubygems"
66 | require "geminabox"
67 |
68 | Geminabox.data = "./data"
69 | run Geminabox::Server
70 |
71 | And run the server:
72 |
73 | [~/dev/geminabox] rackup
74 | [2011-05-19 12:09:40] INFO WEBrick 1.3.1
75 | [2011-05-19 12:09:40] INFO ruby 1.9.2 (2011-02-18) [x86_64-darwin10.5.0]
76 | [2011-05-19 12:09:40] INFO WEBrick::HTTPServer#start: pid=60941 port=9292
77 |
78 | Now you can push gems using the `gem inabox` command. The first time you do
79 | this, you'll be prompted for the location of your gem server.
80 |
81 | [~/dev/secretgem] gem build secretgem.gemspec
82 | Successfully built RubyGem
83 | Name: secretgem
84 | Version: 0.0.1
85 | File: secretgem-0.0.1.gem
86 | [~/dev/secretgem] gem inabox ./secretgem-0.0.1.gem
87 | Enter the root url for your personal geminabox instance. (E.g. http://gems/)
88 | Host: http://localhost:9292
89 | Pushing secretgem-0.0.1.gem to http://localhost:9292/...
90 |
91 | There is a web interface available on
92 | [http://localhost:9292](http://localhost:9292) as well. For more information,
93 | read the [Gem in a box](https://github.com/geminabox/geminabox) README.
94 |
95 | ## Running Gemirro
96 |
97 | If you need a simple application that makes it easy way to create your own
98 | RubyGems mirror without having to push or write all gem you wanted in a
99 | configuration file try out [Gemirro](https://github.com/PierreRambaud/gemirro).
100 | It does mirroring without any authentication and you can add your private
101 | gems in the gems directory. More, to mirroring a source, you only need
102 | to start the server, and gems will automatically be downloaded when needed.
103 |
104 | To get started, install `gemirro`:
105 |
106 | $ gem install gemirro
107 |
108 | The process of setting up a mirror is fairly easy and can be done in few seconds.
109 |
110 | The first step is to set up a new, empty mirror directory.
111 | This is done by running the `gemirro init` command.
112 |
113 | $ gemirro init /srv/http/mirror.com/
114 |
115 | Once created you can edit the main configuration file called `config.rb`.
116 | This configuration file specifies what source to mirror, destination directory, server host and port, etc.
117 |
118 | Once configured and if you add gem in the `define_source`, you can pull them by running the following command:
119 |
120 | $ gemirro update
121 |
122 | Once all the Gems have been downloaded you'll need to generate an index of all the installed files. This can be done as following:
123 |
124 | $ gemirro index
125 |
126 | Last, launch the server, and all requests will check if gems are detected, and download them if necessary and generate index immediately.
127 |
128 | $ gemirro server --start
129 | $ gemirro server --status
130 | $ gemirro server --restart
131 | $ gemirro server --stop
132 |
133 |
134 | A web interface will be available at [http://localhost:2000](http://localhost:2000).
135 |
136 | For more information, read the [Gemirro](https://github.com/PierreRambaud/gemirro) README.
137 |
138 | ## Using gems from your server
139 |
140 | Whether you use Gemstash, Gem in a Box, Gemirro or another gem server, you can
141 | configure RubyGems to use your local or internal source alongside other sources
142 | such as [http://rubygems.org](http://rubygems.org).
143 |
144 | Use the `gem sources` command to add the gem server to your system-wide gem
145 | sources. The following URL is the default for running Gem in a Box via
146 | `rackup`:
147 |
148 | gem sources --add http://localhost:9292
149 |
150 | Then install gems as usual:
151 |
152 | [~] gem install secretgem
153 | Successfully installed secretgem-0.0.1
154 | 1 gem installed
155 |
156 | If you're using [Bundler](https://bundler.io/) then you can specify this
157 | server as a gem source in your `Gemfile`:
158 |
159 | [~/dev/myapp] cat Gemfile
160 | source "http://localhost:9292"
161 | gem "secretgem"
162 |
163 | [~/dev/myapp] bundle
164 | Using secretgem (0.0.1)
165 | Using bundler (1.0.13)
166 | Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
167 |
--------------------------------------------------------------------------------
/api-key-scopes.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: API key scopes
4 | previous: /rubygems-org-rate-limits
5 | next: /run-your-own-gem-server
6 | ---
7 |
8 | RubyGems.org API keys, their scopes, and CLI usage
9 |
10 | You can create multiple API keys based on your requirements. API keys have varying scopes that grant specific privileges. Using API keys with the least amount of privilege makes your RubyGems.org account more secure by limiting the impact a compromised key may have.
11 |
12 | Create a new API key
13 | -----------------------
14 |
15 | Visit your RubyGems.org account [settings page](https://rubygems.org/settings/edit) and click on **API KEYS**. You will be prompted for your account password to confirm your identity.
16 |
17 | {:class="t-img"}
18 |
19 | If you have never visited this page before, you should see at least one key with the name *legacy-key*. The *legacy-key* is relic of the time when RubyGems.org used to have a single API key per account with full access. We recommend that you [migrate away from *legacy-key*](#migration-from-legacy-api-key) as soon as possible.
20 |
21 | Click on **New API Key** to create a new API key for your account.
22 |
23 | {:class="t-img"}
24 |
25 | Enter a name to help you identify the environment the API key may be used in (eg: ci-push-key, mirror-webhook-key, etc.). Check all the scopes you may want to enable and click on create.
26 |
27 | **Note:** *Show dashboard* is an exclusive scope, and it can't be enabled in combination with any other scope.
28 |
29 | {:class="t-img"}
30 |
31 | On the following page, you should see the new API key.
32 |
33 | {:class="t-img"}
34 |
35 | The *Age* column shows how old is the key. The *Last access* column shows the last time (in UTC) the key was used in a successful authentication. You can use the **Edit** button to update the scopes of the key. You can use the **Reset** button in the last row to delete *all* the API keys associated with your account.
36 |
37 | Usage with gem CLI
38 | ------------------
39 |
40 | An API key created as outlined above can be used in the `gem` CLI directly by setting it in an environment variable called `GEM_HOST_API_KEY`, e.g.
41 |
42 | $ GEM_HOST_API_KEY=rubygems_123456 gem push example-1.2.3.gem
43 |
44 | This approach is suitable for non-interactive situations, e.g. for CI/CD-based gem publishing processes. On a personal development machine that you can directly access, the interactive sign-in using `gem signin` (see below) is usually preferrable.
45 |
46 | Creating from gem CLI
47 | ---------------------
48 |
49 | **Note:** You need rubygems 3.2.0 or newer if you like to create API keys with scopes from the gem CLI.
50 |
51 | Running `gem signin` will prompt you for your RubyGems.org credentials, key name, and scopes to enable for the key. The default choice for all scopes is not to enable them.
52 |
53 | $ gem signin
54 | Enter your RubyGems.org credentials.
55 | Don't have an account yet? Create one at https://rubygems.org/sign_up
56 | Email: john@doe.com
57 | Password:
58 |
59 | API Key name [4458ffe32b0c-unknown-user-20201231104303]: docker-push-key
60 | Please select scopes you want to enable for the API key (y/n)
61 | index_rubygems [y/N]:
62 | push_rubygem [y/N]: Y
63 | yank_rubygem [y/N]:
64 | add_owner [y/N]:
65 | remove_owner [y/N]:
66 | access_webhooks [y/N]:
67 | show_dashboard [y/N]:
68 |
69 | Signed in with API key: docker-push-key.
70 |
71 | An API key will automatically be created (default key name: *hostname-whoami-timestamp*) with the required scope when we couldn't find any API key on your host. Similarly, the scope of the existing API key on the host will be updated with the required scope if the key didn't have the correct scope.
72 |
73 | $ gem yank begone -v 4.1.48
74 | Yanking gem from https://rubygems.org...
75 | The existing key doesn't have access of yank_rubygem on https://rubygems.org. Please sign in to update access.
76 | Email: john@doe.com
77 | Password:
78 | Added yank_rubygem scope to the existing API key
79 | Successfully deleted gem: begone (4.1.48)
80 |
81 | API key scopes
82 | --------------
83 |
84 | * [Index rubygems](https://guides.rubygems.org/rubygems-org-api/#get---apiv1gemsjsonyaml): List all RubyGems of your account
85 | * [Push rubygems](https://guides.rubygems.org/rubygems-org-api/#post---apiv1gems): Create a new RubyGem or publish a new version of any RubyGem you own
86 | * [Yank rubygems](https://guides.rubygems.org/rubygems-org-api/#delete---apiv1gemsyank): Remove a published version of any RubyGem you own
87 | * [Add owner](https://guides.rubygems.org/rubygems-org-api/#post---apiv1gemsgem-nameowners): Add a user to owners of any RubyGem you own
88 | * [Remove owner](https://guides.rubygems.org/rubygems-org-api/#delete---apiv1gemsgem-nameowners): Remove a user from owners of any RubyGem you own
89 | * [Access webhooks](https://guides.rubygems.org/rubygems-org-api/#webhook-methods): List, create, delete or fire webhooks associated with your account
90 | * [Show dashboard](https://rubygems.org/dashboard): Access to atom feed of your RubyGems.org dashboard. It is an exclusive scope and can't be enabled with any other scope.
91 |
92 | Scope an API key to a gem
93 | -------------------------
94 | Enabling one or more of the key scopes related to creating or updating a gem ([Push rubygems](https://guides.rubygems.org/rubygems-org-api/#post---apiv1gems), [Yank rubygems](https://guides.rubygems.org/rubygems-org-api/#delete---apiv1gemsyank), [Add owner](https://guides.rubygems.org/rubygems-org-api/#post---apiv1gemsgem-nameowners), and [Remove owner](https://guides.rubygems.org/rubygems-org-api/#delete---apiv1gemsgem-nameowners)) will allow you to scope one of your gems to the API key.
95 | The operations corresponding to these scopes will only be valid on the selected gem.
96 |
97 | {:class="t-img"}
98 |
99 | If you are using a key to modify only one of your gems, please consider gem scoping your keys.
100 |
101 | **Note:** When your ownership to a gem is removed, API keys scoped to that gem will become invalid and cannot be used.
102 |
103 | Enable MFA on specific API keys
104 | -----------------------------
105 |
106 | If your account has MFA enabled on the **UI and gem signin** [authentication level](https://guides.rubygems.org/setting-up-multifactor-authentication/#authentication-levels), you have the option to enable MFA on a specific API key. This will require an OTP code for `gem push`, `yank`, `owner --add/--remove` commands.
107 |
108 | You can toggle this option when creating or editing an API key on the UI.
109 | {:class="t-img"}
110 |
111 | Migration from legacy-api key
112 | -----------------------------
113 |
114 | The legacy API key of your account has been migrated to one with all scopes enabled. We strongly recommend that you delete this key and replace it with a new API key with minimum scopes enabled.
115 |
116 | * Visit [API keys page](https://rubygems.org/profile/api_keys) of your account and click on **delete** button for the key named *legacy-key*.
117 | * Run `gem signout` on all hosts where you have used the legacy API key
118 | * Make sure you have rubygems 3.2.0 or newer installed. Run `gem update --system` to update your rubygem to the latest release.
119 | * Run `gem signin` to create a new API key.
120 |
121 | If it is not possible for you to update your rubygems, you can still use the new API key by creating a new key using the web UI and replacing the key in `~/.gem/credentials` or `~/.local/share/gem/credentials` file.
122 |
123 | $ cat ~/.local/share/gem/credentials
124 | :rubygems_api_key: rubygems_cec9db9373ea171daaaa0bf2337edce187f09558cb19c1b2
125 |
126 | **Note:** The legacy endpoint to fetch API keys, `GET /api/v1/keys` has been updated to create a new API key on each request. As a security precaution, the new API keys are stored in our database after one-way encryption. It is no longer possible for us to fetch the same API key in plain text.
127 |
--------------------------------------------------------------------------------
/security.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Security
4 | url: /security
5 | previous: /publishing
6 | next: /managing-owners-using-ui
7 | ---
8 |
9 | How to build and install cryptographically signed gems-- and other security concerns.
10 |
11 | Security practices are being actively discussed. Check back often.
12 |
13 | * [General](#general)
14 | * [Using Gems](#using-gems)
15 | * [Building Gems](#building-gems)
16 | * [Reporting Security Vulnerabilities](#reporting-security-vulnerabilities)
17 |
18 | General
19 | -------
20 |
21 | Installing a gem allows that gem's code to run in the context of your
22 | application. Clearly this has security implications: installing a malicious gem
23 | on a server could ultimately result in that server being completely penetrated
24 | by the gem's author. Because of this, the security of gem code is a topic of
25 | active discussion within the Ruby community.
26 |
27 | RubyGems has had the ability to [cryptographically sign
28 | gems](https://docs.seattlerb.org/rubygems/Gem/Security.html) since version
29 | 0.8.11. This signing works by using the `gem cert` command to create a key
30 | pair, and then packaging signing data inside the gem itself. The `gem install`
31 | command optionally lets you set a security policy, and you can verify the
32 | signing key for a gem before you install it.
33 |
34 | However, this method of securing gems is not widely used. It requires a number
35 | of [manual steps on the part of the developer](#building-gems), and there is no
36 | well-established chain of trust for gem signing keys. Discussion of new
37 | signing models such as X509 and OpenPGP is going on in the [rubygems-trust
38 | wiki](https://github.com/rubygems-trust/rubygems.org/wiki/_pages), the
39 | [RubyGems-Developers
40 | list](https://groups.google.com/d/msg/rubygems-developers/lnnGTlfsuYo/TLDcJ2RPSDoJ)
41 | and in [IRC](irc://chat.freenode.net/#rubygems-trust). The goal is to improve
42 | (or replace) the signing system so that it is easy for authors and transparent
43 | for users.
44 |
45 | Using Gems
46 | -------
47 |
48 | Install with a trust policy.
49 |
50 | * `gem install gemname -P HighSecurity`: All dependent gems must be signed
51 | and verified.
52 |
53 | * `gem install gemname -P MediumSecurity`: All signed dependent gems must be
54 | verified.
55 |
56 | * `bundle --trust-policy MediumSecurity`: Same as above, except Bundler only
57 | recognizes the long `--trust-policy` flag, not the short `-P`.
58 |
59 | * *Caveat:* Gem certificates are trusted globally, such that adding a
60 | cert.pem for one gem automatically trusts all gems signed by that cert.
61 |
62 | Verify the checksum, if available
63 |
64 | gem fetch gemname -v version
65 | ruby -rdigest/sha2 -e "puts Digest::SHA512.new.hexdigest(File.read('gemname-version.gem'))"
66 |
67 | Know the risks of being pwned, as described by [Benjamin Smith's Hacking with Gems talk](https://youtu.be/zEBReauO-vg)
68 |
69 | Building Gems
70 | -------
71 |
72 | ### Sign with: `gem cert`
73 |
74 | 1) Create self-signed gem cert
75 |
76 | cd ~/.ssh
77 | gem cert --build your@email.com
78 | chmod 600 gem-p*
79 |
80 | - use the email address you specify in your gemspecs
81 |
82 | 2) Configure gemspec with cert
83 |
84 | Add cert public key to your repository
85 |
86 | cd /path/to/your/gem
87 | mkdir certs
88 | cp ~/.ssh/gem-public_cert.pem certs/yourhandle.pem
89 | git add certs/yourhandle.pem
90 |
91 | Add cert paths to your gemspec
92 |
93 | s.cert_chain = ['certs/yourhandle.pem']
94 | s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
95 |
96 | 3) Add your own cert to your approved list, just like anyone else
97 |
98 | gem cert --add certs/yourhandle.pem
99 |
100 | 4) Build gem and test that you can install it
101 |
102 | gem build gemname.gemspec
103 | gem install gemname-version.gem -P HighSecurity
104 | # or -P MediumSecurity if your gem depends on unsigned gems
105 |
106 | 5) Example text for installation documentation
107 |
108 | > MetricFu is cryptographically signed. To be sure the gem you install hasn't been tampered with:
109 | >
110 | > Add my public key (if you haven't already) as a trusted certificate
111 | >
112 | > `gem cert --add <(curl -Ls https://raw.github.com/metricfu/metric_fu/master/certs/bf4.pem)`
113 | >
114 | > `gem install metric_fu -P MediumSecurity`
115 | >
116 | > The MediumSecurity trust profile will verify signed gems, but allow the installation of unsigned dependencies.
117 | >
118 | > This is necessary because not all of MetricFu's dependencies are signed, so we cannot use HighSecurity.
119 |
120 | -------
121 |
122 | ### Include checksum of released gems in your repository
123 |
124 | require 'digest/sha2'
125 | built_gem_path = 'pkg/gemname-version.gem'
126 | checksum = Digest::SHA512.new.hexdigest(File.read(built_gem_path))
127 | checksum_path = 'checksum/gemname-version.gem.sha512'
128 | File.open(checksum_path, 'w' ) {|f| f.write(checksum) }
129 | # add and commit 'checksum_path'
130 |
131 | -------
132 |
133 | ### OpenPGP signing is [not recommended due to lack of support (archive)](https://web.archive.org/web/20131125012205/https://www.rubygems-openpgp-ca.org/blog/nobody-cares-about-signed-gems.html).
134 |
135 | For details, see discussion [with Yorick
136 | Peterse](https://github.com/rubygems/guides/pull/70#issuecomment-29007487).
137 |
138 | Reporting Security vulnerabilities
139 | -------
140 |
141 |
142 | ### Reporting a security vulnerability with someone else's gem
143 |
144 | If you spot a security vulnerability in someone else's gem, then you
145 | first step should be to check whether this is a known vulnerability.
146 | One way is by searching for an advisory on [RubySec](https://rubysec.com).
147 |
148 | If this looks like a newly discovered vulnerability, then you should
149 | contact the author(s) privately (i.e., not via a pull request or issue on a
150 | public project) explaining the issue, how it can be exploited, and ideally
151 | offering an indication of how it might be fixed.
152 |
153 | ### Reporting a security vulnerability with your own gem
154 |
155 | First, request a [CVE identifier](https://www.cve.org/ResourcesSupport/FAQs)
156 | by emailing [one of these places](https://github.com/RedHatProductSecurity/CVE-HOWTO#how-do-i-request-a-cve)
157 | or from GitHub by creating a [Security Advisory](https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/about-repository-security-advisories#cve-identification-numbers).
158 | This identifier will make it easy to uniquely identify the vulnerability when talking about it.
159 |
160 | Second, work out what people who depend on your gem should do to resolve the
161 | vulnerability. This may involve releasing a patched version of your gem that
162 | you can recommend they upgrade to.
163 |
164 | Finally, you need to tell people about the vulnerability. Currently there
165 | is no single place to broadcast this information but some good places to
166 | start might be to:
167 |
168 | - Send an email to several lists including ruby-security-ann@googlegroups.com,
169 | rubysec-announce@googlegroups.com, and oss-security@lists.openwall.com
170 | outlining the vulnerability, which versions of your gem it affects, and what
171 | actions those depending on the gem should take. Make sure to use a subject
172 | that includes the gem name, some short summary of the vulnerability, and the
173 | CVE ID if you have one.
174 |
175 | - Add it to [ruby-advisory-db](https://github.com/rubysec/ruby-advisory-db/).
176 | You can do this by following the
177 | [CONTRIBUTING](https://github.com/rubysec/ruby-advisory-db/blob/master/CONTRIBUTING.md)
178 | guidelines and submitting a pull request.
179 |
180 | Credits
181 | -------
182 |
183 | Several sources were used for content for this guide:
184 |
185 | * [How to cryptographically sign your RubyGem](https://www.benjaminfleischer.com/2013/11/08/how-to-sign-your-rubygem-cert/) - Step-by-step guide
186 | * [Signing rubygems - Pasteable instructions (archive)](https://web.archive.org/web/20130218074304/https://developer.zendesk.com/blog/2013/02/03/signing-gems/)
187 | * [metric_fu gem gemspec](https://github.com/metricfu/metric_fu/blob/master/metric_fu.gemspec)
188 | * [RubyGems Trust Model Overview](https://github.com/rubygems-trust/rubygems.org/wiki/Overview), [doc](https://goo.gl/ybFIO)
189 | * [Let's figure out a way to start signing RubyGems](https://tonyarcieri.com/lets-figure-out-a-way-to-start-signing-rubygems)
190 | * [A Practical Guide to Using Signed Ruby Gems - Part 3: Signing your Own (archive)](https://web.archive.org/web/20131125020053/https://blog.meldium.com/home/2013/3/6/signing-gems-how-to)
191 | * Also see the [Resources](/resources) page.
192 |
--------------------------------------------------------------------------------
/rubygems-basics.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: RubyGems Basics
4 | url: /rubygems-basics
5 | previous: /
6 | next: /what-is-a-gem
7 | ---
8 |
9 | Use of common RubyGems commands
10 |
11 | The `gem` command allows you to interact with RubyGems.
12 |
13 | Ruby 1.9 and newer ships with RubyGems built-in but you may need to upgrade for
14 | bug fixes or new features. To upgrade RubyGems, visit the
15 | [download](https://rubygems.org/pages/download) page.
16 |
17 | If you want to see how to require files from a gem, skip ahead to [What is a
18 | gem](/what-is-a-gem)
19 |
20 | * [Finding Gems](#finding-gems)
21 | * [Installing Gems](#installing-gems)
22 | * [Requiring Code](#requiring-code)
23 | * [Listing Installed Gems](#listing-installed-gems)
24 | * [Uninstalling Gems](#uninstalling-gems)
25 | * [Viewing Documentation](#viewing-documentation)
26 | * [Fetching and Unpacking Gems](#fetching-and-unpacking-gems)
27 | * [Further Reading](#further-reading)
28 |
29 | Finding Gems
30 | ------------
31 |
32 | The `search` command lets you find remote gems by name. You can use regular
33 | expression characters in your query:
34 |
35 | $ gem search ^rails
36 |
37 | *** REMOTE GEMS ***
38 |
39 | rails (7.0.3)
40 | rails-3-settings (0.1.1)
41 | rails-acm (0.1.0)
42 | rails-action-args (0.1.1)
43 | rails-action-authorization (1.1.2)
44 | [...]
45 |
46 | If you see a gem you want more information on you can add the details option.
47 | You'll want to do this with a small number of gems, though, as listing gems
48 | with details requires downloading more files:
49 |
50 | $ gem search ^rails$ -d
51 |
52 | *** REMOTE GEMS ***
53 |
54 | rails (7.0.3)
55 | Author: David Heinemeier Hansson
56 | Homepage: https://rubyonrails.org
57 |
58 | Full-stack web application framework.
59 |
60 | You can also search for gems on rubygems.org such as [this search for
61 | rake](https://rubygems.org/search?query=rake)
62 |
63 | Installing Gems
64 | ---------------
65 |
66 | The `install` command downloads and installs the gem and any necessary
67 | dependencies then builds documentation for the installed gems.
68 |
69 | $ gem install drip
70 | Fetching drip-0.1.1.gem
71 | Fetching rbtree-0.4.5.gem
72 | Building native extensions. This could take a while...
73 | Successfully installed rbtree-0.4.5
74 | Successfully installed drip-0.1.1
75 | Parsing documentation for rbtree-0.4.5
76 | Installing ri documentation for rbtree-0.4.5
77 | Parsing documentation for drip-0.1.1
78 | Installing ri documentation for drip-0.1.1
79 | Done installing documentation for rbtree, drip after 0 seconds
80 | 2 gems installed
81 |
82 | Here the drip command depends upon the rbtree gem which has an extension. Ruby
83 | installs the dependency rbtree and builds its extension, installs the drip gem,
84 | then builds documentation for the installed gems.
85 |
86 | You can disable documentation generation using the `--no-document` argument when
87 | installing gems.
88 |
89 | Requiring code
90 | --------------
91 |
92 | RubyGems modifies your Ruby load path, which controls how your Ruby code is
93 | found by the `require` statement. When you `require` a gem, really you're just
94 | placing that gem's `lib` directory onto your `$LOAD_PATH`. Let's try this out
95 | in `irb`.
96 |
97 | % irb
98 | irb(main):001:0> pp $LOAD_PATH
99 | [".../lib/ruby/site_ruby/3.1.0",
100 | ".../lib/ruby/site_ruby/3.1.0/x86_64-linux",
101 | ".../lib/ruby/site_ruby",
102 | ".../lib/ruby/vendor_ruby/3.1.0",
103 | ".../lib/ruby/vendor_ruby/3.1.0/x86_64-linux",
104 | ".../lib/ruby/vendor_ruby",
105 | ".../lib/ruby/3.1.0",
106 | ".../lib/ruby/3.1.0/x86_64-linux"]
107 |
108 | By default you have just a few system directories on the load path and the Ruby
109 | standard libraries. To add the awesome_print directories to the load path,
110 | you can require one of its files:
111 |
112 | $ gem install awesome_print
113 | [...]
114 | $ irb
115 | irb(main):001:0> require "ap"
116 | => true
117 | irb(main):002:0> pp $LOAD_PATH.first
118 | ".../gems/awesome_print-1.9.2/lib"
119 |
120 | *Tip: Passing `-r` to `irb` will automatically require a library when irb is
121 | loaded.*
122 |
123 | $ irb -rap
124 | irb(main):001:0> ap $LOAD_PATH
125 | [
126 | [0] ".../bundle/gems/awesome_print-1.9.2/lib",
127 | [1] ".../lib/ruby/site_ruby/3.1.0",
128 | [2] ".../lib/ruby/site_ruby/3.1.0/x86_64-linux",
129 | [3] ".../lib/ruby/site_ruby",
130 | [4] ".../lib/ruby/vendor_ruby/3.1.0",
131 | [5] ".../lib/ruby/vendor_ruby/3.1.0/x86_64-linux",
132 | [6] ".../lib/ruby/vendor_ruby",
133 | [7] ".../lib/ruby/3.1.0",
134 | [8] ".../lib/ruby/3.1.0/x86_64-linux"
135 | ]
136 |
137 | Once you've required `ap`, RubyGems automatically places its
138 | `lib` directory on the `$LOAD_PATH`.
139 |
140 | That's basically it for what's in a gem. Drop Ruby code into `lib`, name a
141 | Ruby file the same as your gem (for the gem "freewill" the file should be
142 | `freewill.rb`, see also [name your gem](/name-your-gem)) and it's loadable by
143 | RubyGems.
144 |
145 | The `lib` directory itself normally contains only one `.rb` file and a
146 | directory with the same name as the gem which contains the rest of the files.
147 |
148 | For example:
149 |
150 | % tree freewill/
151 | freewill/
152 | └── lib/
153 | ├── freewill/
154 | │ ├── user.rb
155 | │ ├── widget.rb
156 | │ └── ...
157 | └── freewill.rb
158 |
159 | Listing Installed Gems
160 | ----------------------
161 |
162 | The `list` command shows your locally installed gems:
163 |
164 | $ gem list
165 |
166 | *** LOCAL GEMS ***
167 |
168 | abbrev (default: 0.1.0)
169 | awesome_print (1.9.2)
170 | base64 (default: 0.1.1)
171 | benchmark (default: 0.2.0)
172 | bigdecimal (default: 3.1.1)
173 | bundler (default: 2.3.7)
174 | cgi (default: 0.3.1)
175 | csv (default: 3.2.2)
176 | date (default: 3.2.2)
177 | debug (1.4.0)
178 | delegate (default: 0.2.0)
179 | did_you_mean (default: 1.6.1)
180 | digest (default: 3.1.0)
181 | drb (default: 2.1.0)
182 | drip (0.1.1)
183 | english (default: 0.7.1)
184 | [...]
185 |
186 | The list includes defaults gems and bundled gems both of which were shipped
187 | with Ruby by default. In Ruby 3.1, the default gems are 70 gems in total
188 | including bigdecimal, bundler, csv, did_you_mean etc. and the bundled gems are
189 | debug, rake etc.
190 |
191 | Uninstalling Gems
192 | -----------------
193 |
194 | The `uninstall` command removes the gems you have installed.
195 |
196 | $ gem uninstall drip
197 | Successfully uninstalled drip-0.1.1
198 |
199 | If you uninstall a dependency of a gem RubyGems will ask you for confirmation.
200 |
201 | $ gem uninstall rbtree
202 |
203 | You have requested to uninstall the gem:
204 | rbtree-0.4.5
205 |
206 | drip-0.1.1 depends on rbtree (>= 0)
207 | If you remove this gem, these dependencies will not be met.
208 | Continue with Uninstall? [yN] n
209 | ERROR: While executing gem ... (Gem::DependencyRemovalException)
210 | Uninstallation aborted due to dependent gem(s)
211 |
212 | Viewing Documentation
213 | ---------------------
214 |
215 | You can view the documentation for your installed gems with `ri`:
216 |
217 | $ ri RBTree
218 | = RBTree < MultiRBTree
219 |
220 | (from gem rbtree-0.4.5)
221 | ------------------------------------------------------------------------
222 | A sorted associative collection that cannot contain duplicate keys.
223 | RBTree is a subclass of MultiRBTree.
224 | ------------------------------------------------------------------------
225 |
226 |
227 | Fetching and Unpacking Gems
228 | ---------------------------
229 |
230 | If you wish to audit a gem's contents without installing it you can use the
231 | `fetch` command to download the .gem file then extract its contents with the
232 | `unpack` command.
233 |
234 | $ gem fetch malice
235 | Fetching malice-13.gem
236 | Downloaded malice-13
237 | $ gem unpack malice-13.gem
238 | Unpacked gem: '.../malice-13'
239 | $ more malice-13/README
240 |
241 | Malice v. 13
242 |
243 | DESCRIPTION
244 |
245 | A small, malicious library.
246 |
247 | [...]
248 | $ rm -r malice-13*
249 |
250 | You can also unpack a gem you have installed, modify a few files, then use the
251 | modified gem in place of the installed one:
252 |
253 | $ gem unpack rake
254 | Unpacked gem: '.../13.0.6'
255 | $ vim 13.0.6/lib/rake/...
256 | $ ruby -I 13.0.6/lib -S rake some_rake_task
257 | [...]
258 |
259 | The `-I` argument adds your unpacked rake to the ruby `$LOAD_PATH` which
260 | prevents RubyGems from loading the gem version (or the default version). The
261 | `-S` argument finds `rake` in the shell's `$PATH` so you don't have to type out
262 | the full path.
263 |
264 | Further Reading
265 | ---------------
266 |
267 | This guide only shows the basics of using the `gem` command. For information
268 | on what's inside a gem and how to use one you've installed see the next
269 | section, [What is a gem](/what-is-a-gem). For a complete reference of gem
270 | commands see the [Command Reference](/command-reference).
271 |
--------------------------------------------------------------------------------
/gems-with-extensions.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Gems with Extensions
4 | url: /gems-with-extensions
5 | previous: /make-your-own-gem
6 | next: /name-your-gem
7 | alias: /c-extensions
8 | ---
9 |
10 | Creating a gem that includes an extension that is built at install time.
11 |
12 | Many gems use extensions to wrap libraries that are written in C with a ruby
13 | wrapper. Examples include [nokogiri][nokogiri] which
14 | wraps [libxml2 and libxslt](http://www.xmlsoft.org),
15 | [pg](https://rubygems.org/gems/pg) which is an interface to the [PostgreSQL
16 | database](https://www.postgresql.org) and the
17 | [mysql](https://rubygems.org/gems/mysql) and
18 | [mysql2](https://rubygems.org/gems/mysql2) gems which provide an interface to
19 | the [MySQL database](https://www.mysql.com).
20 |
21 | Creating a gem that uses an extension involves several steps. This guide will
22 | focus on what you should put in your gem specification to make this as easy and
23 | maintainable as possible. The extension in this guide will wrap `malloc()` and
24 | `free()` from the C standard library.
25 |
26 | Gem layout
27 | ----------
28 |
29 | Every gem should start with a Rakefile which contains the tasks needed by
30 | developers to work on the gem. The files for the extension should go in the
31 | `ext/` directory in a directory matching the extension's name. For this
32 | example we'll use "my_malloc" for the name.
33 |
34 | Some extensions will be partially written in C and partially written in ruby.
35 | If you are going to support multiple languages, such as C and Java extensions,
36 | you should put the C-specific ruby files under the `ext/` directory as well in a
37 | `lib/` directory.
38 |
39 | Rakefile
40 | ext/my_malloc/extconf.rb # extension configuration
41 | ext/my_malloc/my_malloc.c # extension source
42 | lib/my_malloc.rb # generic features
43 |
44 | When the extension is built the files in `ext/my_malloc/lib/` will be installed
45 | into the `lib/` directory for you.
46 |
47 | extconf.rb
48 | ----------
49 |
50 | The extconf.rb configures a Makefile that will build your extension. The
51 | extconf.rb must check for the necessary functions, macros and shared libraries
52 | your extension depends upon. The extconf.rb must exit with an error if any of
53 | these are missing.
54 |
55 | Here is an extconf.rb that checks for `malloc()` and `free()` and creates a
56 | Makefile that will install the built extension at `lib/my_malloc/my_malloc.so`:
57 |
58 | require "mkmf"
59 |
60 | abort "missing malloc()" unless have_func "malloc"
61 | abort "missing free()" unless have_func "free"
62 |
63 | create_makefile "my_malloc/my_malloc"
64 |
65 | See the [mkmf documentation][mkmf.rb] and [extension.rdoc][extension.rdoc] for further
66 | information about creating an extconf.rb and for documentation on these
67 | methods.
68 |
69 | C Extension
70 | -----------
71 |
72 | The C extension that wraps `malloc()` and `free()` goes in
73 | `ext/my_malloc/my_malloc.c`. Here's the listing:
74 |
75 | #include
76 |
77 | struct my_malloc {
78 | size_t size;
79 | void *ptr;
80 | };
81 |
82 | static void
83 | my_malloc_free(void *p) {
84 | struct my_malloc *ptr = p;
85 |
86 | if (ptr->size > 0)
87 | free(ptr->ptr);
88 | }
89 |
90 | static VALUE
91 | my_malloc_alloc(VALUE klass) {
92 | VALUE obj;
93 | struct my_malloc *ptr;
94 |
95 | obj = Data_Make_Struct(klass, struct my_malloc, NULL, my_malloc_free, ptr);
96 |
97 | ptr->size = 0;
98 | ptr->ptr = NULL;
99 |
100 | return obj;
101 | }
102 |
103 | static VALUE
104 | my_malloc_init(VALUE self, VALUE size) {
105 | struct my_malloc *ptr;
106 | size_t requested = NUM2SIZET(size);
107 |
108 | if (0 == requested)
109 | rb_raise(rb_eArgError, "unable to allocate 0 bytes");
110 |
111 | Data_Get_Struct(self, struct my_malloc, ptr);
112 |
113 | ptr->ptr = malloc(requested);
114 |
115 | if (NULL == ptr->ptr)
116 | rb_raise(rb_eNoMemError, "unable to allocate %ld bytes", requested);
117 |
118 | ptr->size = requested;
119 |
120 | return self;
121 | }
122 |
123 | static VALUE
124 | my_malloc_release(VALUE self) {
125 | struct my_malloc *ptr;
126 |
127 | Data_Get_Struct(self, struct my_malloc, ptr);
128 |
129 | if (0 == ptr->size)
130 | return self;
131 |
132 | ptr->size = 0;
133 | free(ptr->ptr);
134 |
135 | return self;
136 | }
137 |
138 | void
139 | Init_my_malloc(void) {
140 | VALUE cMyMalloc;
141 |
142 | cMyMalloc = rb_const_get(rb_cObject, rb_intern("MyMalloc"));
143 |
144 | rb_define_alloc_func(cMyMalloc, my_malloc_alloc);
145 | rb_define_method(cMyMalloc, "initialize", my_malloc_init, 1);
146 | rb_define_method(cMyMalloc, "free", my_malloc_release, 0);
147 | }
148 |
149 | This extension is simple with just a few parts:
150 |
151 | * `struct my_malloc` to hold the allocated memory
152 | * `my_malloc_free()` to free the allocated memory after garbage collection
153 | * `my_malloc_alloc()` to create the ruby wrapper object
154 | * `my_malloc_init()` to allocate memory from ruby
155 | * `my_malloc_release()` to free memory from ruby
156 | * `Init_my_malloc()` to register the functions in the `MyMalloc` class.
157 |
158 | Now we can create the actual `MyMalloc` class and bind newly defined methods
159 | in Ruby (`lib/my_malloc.rb` is the correct place for that), e.g.:
160 |
161 | class MyMalloc
162 | VERSION = "1.0"
163 | end
164 |
165 | require "my_malloc/my_malloc"
166 |
167 |
168 | You can test building the extension as follows:
169 |
170 | $ cd ext/my_malloc
171 | $ ruby extconf.rb
172 | checking for malloc()... yes
173 | checking for free()... yes
174 | creating Makefile
175 | $ make
176 | compiling my_malloc.c
177 | linking shared-object my_malloc.bundle
178 | $ cd ../..
179 | $ ruby -Ilib:ext -r my_malloc -e "p MyMalloc.new(5).free"
180 | #
181 |
182 | But this will get tedious after a while. Let's automate it!
183 |
184 | rake-compiler
185 | -------------
186 |
187 | [rake-compiler][rake-compiler] is a set of rake
188 | tasks for automating extension building. rake-compiler can be used with C or
189 | Java extensions in the same project ([nokogiri][nokogiri] uses it this way).
190 |
191 | First install the gem:
192 |
193 | $ gem install rake-compiler
194 |
195 | Adding rake-compiler to the `Rakefile` is very simple:
196 |
197 | require "rake/extensiontask"
198 |
199 | Rake::ExtensionTask.new "my_malloc" do |ext|
200 | ext.lib_dir = "lib/my_malloc"
201 | end
202 |
203 | Now you can build the extension with `rake compile` and hook the compile task
204 | into other tasks (such as tests).
205 |
206 | Setting `lib_dir` places the shared library in `lib/my_malloc/my_malloc.so` (or
207 | `.bundle` or `.dll`). This allows the top-level file for the gem to be a ruby
208 | file. This allows you to write the parts that are best suited to ruby in ruby.
209 |
210 | For example:
211 |
212 | class MyMalloc
213 |
214 | VERSION = "1.0"
215 |
216 | end
217 |
218 | require "my_malloc/my_malloc"
219 |
220 | Setting the `lib_dir` also allows you to build a gem that contains pre-built
221 | extensions for multiple versions of ruby. (An extension for Ruby 1.9.3 cannot
222 | be used with an extension for Ruby 2.0.0). `lib/my_malloc.rb` can pick the
223 | correct shared library to install.
224 |
225 | Gem specification
226 | -----------------
227 |
228 | The final step to building the gem is adding the extconf.rb to the extensions
229 | list in the gemspec:
230 |
231 | Gem::Specification.new "my_malloc", "1.0" do |s|
232 | # [...]
233 |
234 | s.extensions = %w[ext/my_malloc/extconf.rb]
235 | end
236 |
237 | Now you can build and release the gem!
238 |
239 | Extension Naming
240 | ----------------
241 |
242 | To avoid unintended interactions between gems, it's a good idea for each gem to
243 | keep all of its files in a single directory. Here are the recommendations for
244 | a gem with the name ``:
245 |
246 | 1. `ext/` is the directory that contains the source files and
247 | `extconf.rb`
248 | 2. `ext//.c` is the main source file (there may be others)
249 | 3. `ext//.c` contains a function `Init_`. (The name
250 | following `Init_` function must exactly match the name of the extension for
251 | it to be loadable by require.)
252 | 4. `ext//extconf.rb` calls `create_makefile('/')` only when
253 | the all the pieces needed to compile the extension are present.
254 | 5. The gemspec sets `extensions = ['ext//extconf.rb']` and includes any
255 | of the necessary extension source files in the `files` list.
256 | 6. `lib/.rb` contains `require '/'` which loads the C
257 | extension
258 |
259 | Further Reading
260 | ---------------
261 |
262 | * [my_malloc](https://github.com/rubygems/guides/tree/my_malloc) contains the
263 | source for this extension with some additional comments.
264 | * [extension.rdoc][extension.rdoc] describes in greater detail how to build
265 | extensions in ruby
266 | * [MakeMakefile][mkmf.rb] contains documentation for mkmf.rb, the library
267 | extconf.rb uses to detect ruby and C library features
268 | * [rake-compiler][rake-compiler] integrates building C and Java extensions into
269 | your Rakefile in a smooth manner.
270 | * [Writing C extensions part
271 | 1](https://tenderlovemaking.com/2009/12/18/writing-ruby-c-extensions-part-1.html)
272 | and [part 2](https://tenderlovemaking.com/2010/12/11/writing-ruby-c-extensions-part-2.html))
273 | by Aaron Patterson
274 | * Interfaces to C libraries can be written using ruby and
275 | [fiddle](https://docs.ruby-lang.org/en/master/Fiddle.html) (part
276 | of the standard library) or [ruby-ffi](https://github.com/ffi/ffi)
277 |
278 | [extension.rdoc]: https://github.com/ruby/ruby/blob/master/doc/extension.rdoc
279 | [mkmf.rb]: https://github.com/ruby/ruby/blob/master/lib/mkmf.rb
280 | [rake-compiler]: https://github.com/luislavena/rake-compiler
281 | [nokogiri]: https://rubygems.org/gems/nokogiri
282 |
--------------------------------------------------------------------------------