├── 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 |
12 | ⚠️ 13 | Private Beta: 14 | Organizations are currently in limited private beta testing. If you're interested in joining the beta program, please contact support@rubygems.org. 15 |
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 | ![MFA status indicators](/images/mfa-required-since.png){: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 | ![Nickname for security device on the edit settings page](/images/enabling_webauthn_nickname.png){: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/?port= 28 | to authenticate via security device. If you can't verify using WebAuthn but 29 | have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` 30 | option. 31 | 32 | Depending on your terminal program, you may be able to click, command-click or 33 | control-click on the link to open it in your default browser. Otherwise you will 34 | need to copy and paste the link into a new tab. 35 | 36 | A webpage titled "Authenticate with Security Device" appears. Click "Authenticate". 37 | Your browser will show a popup asking you to use a Passkey or other authentication 38 | device (the exact popup will vary according to the browser). 39 | 40 | Once you have authenticated using your WebAuthn device device, you will see a 41 | "Success" page. At this point you can close your browser tab and return to the 42 | command line, which will say: 43 | 44 | You are verified with a security device. You may close the browser window. 45 | Signed in with API key: 46 | -------------------------------------------------------------------------------- /rdoc/generator/template/jekdoc/classpage.rhtml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Specification Reference 4 | url: /specification-reference 5 | previous: /patterns 6 | next: /command-reference 7 | --- 8 | 9 | <% description = klass.description 10 | description.gsub! %r%(.*?)%m, '\2' 11 | description.gsub! %r%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 |
12 | ⚠️ 13 | Private Beta: 14 | Organizations are currently in limited private beta testing. If you're interested in joining the beta program, please contact support@rubygems.org. 15 |
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 | ![User's pending trusted publisher page with a create button](/images/trusted-publishing/pending-trusted-publisher-create.png){:class="t-img"} 23 | 24 | Click the "Create" button, which will take you to the publisher configuration page. 25 | 26 | ![Pending trusted publisher creation form](/images/trusted-publishing/pending-trusted-publisher-form.png){: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 | ![Pending trusted publisher creation form with values filled in](/images/trusted-publishing/pending-trusted-publisher-form-filled.png){: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 | ![List of configured pending trusted publishers](/images/trusted-publishing/pending-trusted-publishers-index.png){: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 | ![List of gems on a RubyGems.org profile](/images/trusted-publishing/profile-gem-list.png){: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 | ![Links shown on the sidebar of a gem page when the user is an owner](/images/trusted-publishing/gem-owner-sidebar-links.png){:class="t-img t-img--small"} 18 | 19 | This will take you to the gem's trusted publishers page. 20 | 21 | ![Gem's trusted publisher page with a create button](/images/trusted-publishing/rubygem-trusted-publisher-create.png){:class="t-img"} 22 | 23 | Click the "Create" button, which will take you to the publisher configuration page. 24 | 25 | ![Gem trusted publisher creation form](/images/trusted-publishing/rubygem-trusted-publisher-form.png){: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 | ![List of configured gem trusted publishers](/images/trusted-publishing/rubygem-trusted-publishers-index.png){: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 | ![Multi-factor authentication section on the edit settings page](/images/enabling_mfa_step1.png){: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 | ![Registering a new device](/images/enabling_mfa_step2.png){: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 | ![Recovery codes](/images/enabling_mfa_step3.png){:class="t-img"} 39 | 4. Sign out and sign in again. Signing in will now ask for an OTP code. 40 | ![OTP prompt at login page](/images/mfa_login.png){: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 |
12 | ⚠️ 13 | Private Beta: 14 | Organizations are currently in limited private beta testing. If you're interested in joining the beta program, please contact support@rubygems.org. 15 |
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 | ![Multi-factor section on the edit settings page](/images/changing_mfa_step1.png){:class="t-img"} 39 | 2. You will be prompted to use your MFA device to authorize the MFA level change. 40 | ![Multi-factor authentication prompt to update MFA level](/images/changing_mfa_step2.png){: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 instead of the current working directory.\n", 70 | " `-C PATH` Run as if gem build was started in instead of the current working directory.\n", 71 | 'short option only with argument', 72 | ], 73 | ].each do |given,expected,description| 74 | it (description || given) do 75 | expected = given if expected.nil? 76 | 77 | _(subject.markdownize_options(given)).must_equal expected 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /what-is-a-gem.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: What is a gem? 4 | url: /what-is-a-gem/ 5 | previous: /rubygems-basics/ 6 | next: /make-your-own-gem/ 7 | --- 8 | 9 | Unpack the mystery behind what's in a RubyGem. 10 | 11 | Structure of a Gem 12 | ------------------ 13 | 14 | Each gem has a name, version, and platform. For example, the 15 | [rake](https://rubygems.org/gems/rake) gem has a `13.0.6` version (from Jul 16 | 2021). Rake's platform is `ruby`, which means it works on any platform Ruby 17 | runs on. 18 | 19 | Platforms are based on the CPU architecture, operating system type and 20 | sometimes the operating system version. Examples include "x86-mingw32" or 21 | "java". The platform indicates the gem only works with a ruby built for the 22 | same platform. RubyGems will automatically download the correct version for 23 | your platform. See `gem help platform` for full details. 24 | 25 | Inside gems are the following components: 26 | 27 | * Code (including tests and supporting utilities) 28 | * Documentation 29 | * gemspec 30 | 31 | Each gem follows the same standard structure of code organization: 32 | 33 | % tree freewill 34 | freewill/ 35 | ├── bin/ 36 | │ └── freewill 37 | ├── lib/ 38 | │ └── freewill.rb 39 | ├── test/ 40 | │ └── test_freewill.rb 41 | ├── README 42 | ├── Rakefile 43 | └── freewill.gemspec 44 | 45 | Here, you can see the major components of a gem: 46 | 47 | * The `lib` directory contains the code for the gem 48 | * The `test` or `spec` directory contains tests, depending on which test 49 | framework the developer uses 50 | * A gem usually has a `Rakefile`, which the 51 | [rake](https://rubygems.org/gems/rake) program uses to automate tests, 52 | generate code, and perform other tasks. 53 | * This gem also includes an executable file in the 54 | `bin` directory, which will be loaded into the user's `PATH` when the gem is 55 | installed. 56 | * Documentation is usually included in the `README` and inline with the code. 57 | When you install a gem, documentation is generated automatically for you. 58 | Most gems include [RDoc](https://ruby.github.io/rdoc/) documentation, 59 | but some use [YARD](https://yardoc.org/) docs instead. 60 | * The final piece is the gemspec, which contains information about the gem. 61 | The gem's files, test information, platform, version number and more are all 62 | laid out here along with the author's email and name. 63 | 64 | [More information on the gemspec file](/specification-reference/) 65 | 66 | [Building your own gem](/make-your-own-gem/) 67 | 68 | The Gemspec 69 | ----------- 70 | 71 | The gemspec specifies the information about a gem such as its name, version, 72 | description, authors and homepage. 73 | 74 | Here's an example of a gemspec file. You can learn more in [how to make a 75 | gem](/make-your-own-gem/). 76 | 77 | % cat freewill.gemspec 78 | Gem::Specification.new do |s| 79 | s.name = 'freewill' 80 | s.version = '1.0.0' 81 | s.summary = "Freewill!" 82 | s.description = "I will choose Freewill!" 83 | s.authors = ["Nick Quaranto"] 84 | s.email = 'nick@quaran.to' 85 | s.homepage = 'http://example.com/freewill' 86 | s.files = ["lib/freewill.rb", ...] 87 | end 88 | 89 | For more information on the gemspec, please check out the full [Specification 90 | Reference](/specification-reference/) which goes over each metadata field in 91 | detail. 92 | 93 | Credits 94 | ------- 95 | 96 | This guide was adapted from [Gonçalo 97 | Silva](https://twitter.com/goncalossilva)'s original tutorial on 98 | docs.rubygems.org and from Gem Sawyer, Modern Day Ruby Warrior. 99 | -------------------------------------------------------------------------------- /using-s3-source.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Using S3 as gem source 4 | url: /using-s3-source 5 | previous: /mfa-requirement-opt-in 6 | next: /resources 7 | --- 8 | How to use S3 bucket as gem source. 9 | 10 | [Gem server solutions](/run-your-own-gem-server) with their wide feature set, come in very handy for usecases like private hosting, mirroring and inter-release builds. With s3 bucket as your gem source, you get convenience of a private gem server, without the hassle of running or maintaining a host. In this guide, we cover steps required for setting up a private gem source using a s3 bucket and configuration for its use with the `gem` command. 11 | > Please check [s3 documentation](https://docs.aws.amazon.com/s3/index.html) if you would like to learn about creating a s3 buckets and their pricing. 12 | 13 | Make sure you are running on ruby gems version that supports s3 signing. You can update your ruby gems with the following command: 14 | 15 | $ gem update --system 16 | 17 | ## Setting up repo 18 | 19 | For a static gem source, you will need 4 additional files beside the `.gem` file: 20 | 21 | - `specs..gz` 22 | - `latest_specs..gz` 23 | - `prerelease_specs..gz` 24 | - `quick/Marshal./.gemspec.rz` 25 | 26 | You can generate all of them using one command: `gem generate_index`. 27 | 28 | $ mkdir ~/repo && cd ~/repo 29 | 30 | # .gem must exist in a directory named `gems` 31 | $ mkdir gems && wget -P gems/ https://rubygems.org/downloads/rake-12.3.2.gem 32 | 33 | $ gem generate_index --directory . 34 | 35 | # replace bucket1 with name of the bucket you created 36 | $ aws s3 sync . s3://bucket1 37 | 38 | 39 | ## Use with gem command 40 | 41 | > It's good practice to create a separate IAM user with only read rights on the S3 bucket. Use a other IAM user for pushing the gems with write rights. 42 | 43 | You can use your s3 source using `--source` flag: 44 | 45 | $ gem install rake -v 12.3.2 --source s3://:@bucket1 46 | 47 | Use `.gemrc` if you would like to pre configure multiple s3 sources. It also helps avoid issues related to special characters in the secret key and allows you to specify s3 bucket region. 48 | 49 | Add your s3 source under `:sources` key. Each s3 bucket should have its own set of credentials in a hash under `s3_source` key. You can use one of the providers to extract AWS credentials: 50 | - `env` - [AWS environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) 51 | - `instance_profile` - [AWS EC2 Instance Metadata](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) - will only work on the actual EC2 instance 52 | 53 | Or set AWS access id, secret and session token explicitly. 54 | 55 | > Note that you need to add `/` to your s3 source uri, if your gem repo doesn't exist at the root of the bucket. NOTE: The trailing slash. 56 | 57 | $ cat ~/.gemrc 58 | :sources: 59 | - s3://bucket1/ 60 | - s3://bucket2/ 61 | - s3://bucket3/path_to_gems_dir/ 62 | - s3://bucket4/ 63 | - https://rubygems.org/ 64 | :s3_source: 65 | :bucket1: 66 | :provider: env 67 | :bucket2: 68 | :provider: instance_profile 69 | :region: us-west-2 70 | :bucket3: 71 | :id: AOUEAOEU123123AOEUAO 72 | :secret: aodnuhtdao/saeuhto+19283oaehu/asoeu+123h 73 | :region: us-east-2 74 | :bucket4: 75 | :id: AOUEAOEU123123AOEUAO 76 | :secret: aodnuhtdao/saeuhto+19283oaehu/asoeu+123h 77 | :security_token: AQoDYXdzEJr 78 | :region: us-west-1 79 | 80 | #### Read more: 81 | 82 | [Setting up Travis for inter-release builds](https://simonwo.net/code/gem-server-in-s3/) 83 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | gem "rdoc" 2 | 3 | require 'rdoc/rdoc' 4 | require 'rdoc/task' 5 | require 'fileutils' 6 | 7 | require_relative 'lib/options_list_markdownizer' 8 | 9 | $:.unshift '.', '../rubygems/lib' 10 | 11 | ENV['RUBYGEMS_DIR'] ||= File.expand_path '../../..', __FILE__ 12 | 13 | task :RUBYGEMS_DIR_exists do 14 | message = <<-NO_RUBYGEMS_DIR 15 | The Rubygems rdocs are required to build the spec guide. 16 | 17 | Install or clone it from GitHub, then: 18 | 19 | RUBYGEMS_DIR=/path/to/rubygems/source rake spec_guide --trace 20 | 21 | The RUBYGEMS_DIR is assumed to exist at: 22 | 23 | #{ENV['RUBYGEMS_DIR']} 24 | NO_RUBYGEMS_DIR 25 | 26 | abort message unless File.exist? ENV['RUBYGEMS_DIR'] 27 | end 28 | 29 | # RUBYGEMS_DIR should be checked first 30 | task rdoc_spec: %w[RUBYGEMS_DIR_exists] 31 | 32 | RDoc::Task.new(:rdoc_spec) do |rd| 33 | spec_file = File.join(ENV["RUBYGEMS_DIR"].to_s, "lib", "rubygems", "specification.rb") 34 | rd.rdoc_files.include(spec_file) 35 | rd.template = "jekdoc" 36 | rd.options << '--quiet' 37 | end 38 | 39 | desc "move spec guide into the right place" 40 | task :move_spec => %w[specification-reference.md] 41 | 42 | file 'html/Gem/Specification.html' => %w[rdoc_spec] 43 | 44 | file 'specification-reference.md' => %w[html/Gem/Specification.html] do 45 | cp 'html/Gem/Specification.html', 'specification-reference.md' 46 | end 47 | 48 | desc "clean up after rdoc" 49 | task :clean do 50 | FileUtils.rm_rf "html" 51 | end 52 | 53 | desc "generate specification guide" 54 | task :spec_guide => [:rdoc_spec, :move_spec, :clean] 55 | 56 | desc "generate command guide" 57 | task :command_guide => %w[command-reference.md] 58 | 59 | command_reference_files = Rake::FileList.new(*%W[ 60 | Rakefile 61 | command-reference.erb 62 | #{ENV['RUBYGEMS_DIR']}/lib/rubygems.rb 63 | #{ENV['RUBYGEMS_DIR']}/lib/rubygems/command_manager.rb 64 | #{ENV['RUBYGEMS_DIR']}/lib/rubygems/commands/*.rb 65 | ]) 66 | 67 | file 'command-reference.md' => 68 | %w[RUBYGEMS_DIR_exists] + command_reference_files do 69 | require 'rubygems/command_manager' 70 | require 'rdoc/erbio' 71 | 72 | names = Gem::CommandManager.instance.command_names 73 | commands = {} 74 | names.each do |name| 75 | command = Gem::CommandManager.instance[name] 76 | command.options[:help] = '' 77 | commands[name] = command 78 | end 79 | 80 | def htmlify(string) 81 | lines = string.split("\n") 82 | html_string = '' 83 | lines.each do |line| 84 | if line 85 | if line =~ /^ / 86 | # This will end up in a
 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 | 
12 | ⚠️ 13 | Private Beta: 14 | Organizations are currently in limited private beta testing. If you're interested in joining the beta program, please contact support@rubygems.org. 15 |
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 | ![Rubygem page](/images/managing-owners-using-ui/rubygem-page.png){: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 | ![Confirm Password](/images/managing-owners-using-ui/confirm-password.png){: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 | ![Owners Index](/images/managing-owners-using-ui/owners-index.png){: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 | ![Resend confirmation](/images/managing-owners-using-ui/rubygem-resend-confirmation.png){: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 |
10 | ⚠️ 11 | Private Beta: 12 | Organizations are currently in limited private beta testing. If you're interested in joining the beta program, please contact support@rubygems.org. 13 |
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 | ![Settings API key](/images/settings-api-key.png){: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 | ![Settings API key](/images/api-keys-index.png){: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 | ![New API key](/images/new-api-key.png){:class="t-img"} 30 | 31 | On the following page, you should see the new API key. 32 | 33 | ![API key created](/images/api-key-created.png){: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 | ![New API key with gem scope](/images/new-api-key-gem-scope.png){: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 | ![New API key with MFA enabled](/images/new-mfa-api-key.png){: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 | --------------------------------------------------------------------------------