| 1 | --- |
| 2 | title: RubyGems |
| 3 | description: Push a workspace's Ruby gems with gem push and install them with Bundler, from a gem registry of its own on g1t.sh, from your machine and from workflows. |
| 4 | --- |
| 5 | |
| 6 | Every workspace has a gem registry of its own. `gem push` publishes to it, |
| 7 | and Bundler and `gem install` install from it, private gems included. |
| 8 | It serves both indexes RubyGems reads: the compact index Bundler uses |
| 9 | (`versions`, `info/<gem>`), and the full index (`specs.4.8.gz` and each |
| 10 | version's specification) that `gem install --source` and `gem search` |
| 11 | read. |
| 12 | |
| 13 | ```text |
| 14 | https://g1t.sh/-/rubygems/<workspace>/ |
| 15 | ``` |
| 16 | |
| 17 | Gems from rubygems.org still come from rubygems.org; only the gems you |
| 18 | name with the workspace's source come from g1t. |
| 19 | |
| 20 | ## Push |
| 21 | |
| 22 | Say in the gemspec which repository the gem comes from: |
| 23 | |
| 24 | ```ruby |
| 25 | Gem::Specification.new do |spec| |
| 26 | spec.name = "http-client" |
| 27 | spec.version = "0.3.1" |
| 28 | spec.summary = "Our HTTP client" |
| 29 | spec.authors = ["Ada"] |
| 30 | spec.files = Dir["lib/**/*.rb"] |
| 31 | spec.homepage = "https://g1t.sh/acme/http-client" |
| 32 | spec.metadata["source_code_uri"] = "https://g1t.sh/acme/http-client" |
| 33 | spec.metadata["allowed_push_host"] = "https://g1t.sh/-/rubygems/acme" |
| 34 | end |
| 35 | ``` |
| 36 | |
| 37 | Build it and push it with an [access token](https://g1t.sh/settings/tokens) |
| 38 | as the API key: |
| 39 | |
| 40 | ```sh |
| 41 | gem build http-client.gemspec |
| 42 | GEM_HOST_API_KEY=<token> gem push http-client-0.3.1.gem --host https://g1t.sh/-/rubygems/acme |
| 43 | ``` |
| 44 | |
| 45 | To keep the key instead of passing it each time, add it to |
| 46 | `~/.gem/credentials`, under the registry's address: |
| 47 | |
| 48 | ```yaml |
| 49 | --- |
| 50 | :https://g1t.sh/-/rubygems/acme: g1t_... |
| 51 | ``` |
| 52 | |
| 53 | `allowed_push_host` keeps the gem from being pushed to rubygems.org by |
| 54 | mistake. A token with full access works; one with scopes needs |
| 55 | `packages:write` to push and yank, and `packages:read` to install private |
| 56 | gems. |
| 57 | |
| 58 | The first push makes the gem. When its `source_code_uri` (or `homepage`) |
| 59 | is a g1t.sh repository of the same workspace, or a repository is named |
| 60 | like the gem (a gem `http_client` is also matched to a repository |
| 61 | `http-client`), it is linked to that repository and has its visibility and |
| 62 | roles: pushing needs Write on it. Otherwise it is the workspace's, |
| 63 | private, and needs the workspace's Write base permission. See |
| 64 | [who can see and publish a package](/guides/packages/#who-can-see-and-publish-a-package). |
| 65 | |
| 66 | The gem's page on g1t.sh shows the summary of its highest stable version. |
| 67 | |
| 68 | ## Install with Bundler |
| 69 | |
| 70 | Name the workspace's source for the gems that come from it: |
| 71 | |
| 72 | ```ruby |
| 73 | source "https://rubygems.org" |
| 74 | |
| 75 | source "https://g1t.sh/-/rubygems/acme/" do |
| 76 | gem "http-client", "~> 0.3" |
| 77 | end |
| 78 | ``` |
| 79 | |
| 80 | or let Bundler write that for you: |
| 81 | |
| 82 | ```sh |
| 83 | bundle add http-client --source https://g1t.sh/-/rubygems/acme/ |
| 84 | ``` |
| 85 | |
| 86 | For private gems, give Bundler a username (any works) and a token, for |
| 87 | the source's address: |
| 88 | |
| 89 | ```sh |
| 90 | bundle config set --global https://g1t.sh/-/rubygems/acme/ ada:<token> |
| 91 | ``` |
| 92 | |
| 93 | Or set them once for every workspace on g1t.sh, by host: |
| 94 | `bundle config set --global g1t.sh ada:<token>`, which can also come from |
| 95 | the environment as `BUNDLE_G1T__SH`, as [workflows](#in-workflows) give it. |
| 96 | |
| 97 | `bundle install` then reads the compact index and downloads each `.gem`, |
| 98 | which it checks against the SHA-256 the index names. |
| 99 | |
| 100 | ## Install with gem |
| 101 | |
| 102 | To install a gem and what it depends on without a `Gemfile`, name the |
| 103 | workspace's source. For private gems, put a username (any works) and a |
| 104 | token in its address: |
| 105 | |
| 106 | ```sh |
| 107 | gem install http-client --source https://ada:<token>@g1t.sh/-/rubygems/acme/ |
| 108 | ``` |
| 109 | |
| 110 | Gems it depends on from rubygems.org need that source too: add |
| 111 | `--source https://rubygems.org/` after the workspace's. To keep the |
| 112 | source, add it once with `gem sources --add <address>`. |
| 113 | |
| 114 | `gem search http --remote --source <address>` lists the workspace's gems, |
| 115 | and `gem specification http-client --remote --source <address>` shows one. |
| 116 | `--prerelease` includes pre-releases. Yanked versions are in neither. |
| 117 | |
| 118 | ## Names and versions |
| 119 | |
| 120 | A gem's name is letters, digits, `.`, `-` and `_`, with at least one |
| 121 | letter. In a workspace, names that differ only in case are one name: once |
| 122 | `http-client` is pushed, `HTTP-Client` is refused. |
| 123 | |
| 124 | A version is pushed once, for each platform: pushing `0.3.1` again is |
| 125 | refused with `409`, even after it is yanked, so bump `version` first. A |
| 126 | gem built for a platform (`0.3.1-x86_64-linux`) is its own version beside |
| 127 | the `ruby` one. A version with a letter in it (`0.4.0.rc1`) is a |
| 128 | pre-release, which Bundler picks only when asked for. |
| 129 | |
| 130 | ## Yank |
| 131 | |
| 132 | ```sh |
| 133 | GEM_HOST_API_KEY=<token> gem yank http-client --version 0.3.1 --host https://g1t.sh/-/rubygems/acme |
| 134 | ``` |
| 135 | |
| 136 | A yanked version leaves both indexes, so Bundler and `gem install` no |
| 137 | longer resolve to it, |
| 138 | but its `.gem` is still downloaded for a `Gemfile.lock` that names it. |
| 139 | Yanking needs what pushing does. The gem's page marks yanked versions, and |
| 140 | someone with Admin on the linked repository (an owner, for the |
| 141 | workspace's own gems) can delete a version there for good. |
| 142 | |
| 143 | ## Private and public gems |
| 144 | |
| 145 | A gem linked to a repository has the repository's visibility; one of the |
| 146 | workspace's own is private until an owner makes it public on its page. |
| 147 | |
| 148 | | The workspace's gems | Without credentials | With credentials | |
| 149 | | --- | --- | --- | |
| 150 | | All public | Bundler and `gem` read the index and download them. | The same; the key is sent to push and yank. | |
| 151 | | Some private | The registry answers `401`: Bundler asks for credentials for the source, and `gem` needs them in the source's address. | Each gem the credentials' owner may see. | |
| 152 | |
| 153 | A private gem you cannot see looks exactly like one that does not exist. |
| 154 | |
| 155 | ## In workflows |
| 156 | |
| 157 | A workflow's `G1T_TOKEN` is the workspace's own token for the run, and can |
| 158 | install and push the workspace's gems: |
| 159 | |
| 160 | ```yaml |
| 161 | jobs: |
| 162 | publish: |
| 163 | runs-on: ubuntu-latest |
| 164 | env: |
| 165 | GEM_HOST_API_KEY: ${{ secrets.G1T_TOKEN }} |
| 166 | BUNDLE_G1T__SH: g1t:${{ secrets.G1T_TOKEN }} |
| 167 | steps: |
| 168 | - uses: actions/checkout@v4 |
| 169 | - run: bundle install && bundle exec rake test |
| 170 | - run: gem build http-client.gemspec |
| 171 | - run: gem push http-client-*.gem --host https://g1t.sh/-/rubygems/acme |
| 172 | ``` |
| 173 | |
| 174 | ## Size |
| 175 | |
| 176 | A push is one request with the `.gem` as its body, and may hold at most |
| 177 | 100 MB. Without the [g1t plan](/guides/usage-and-billing/#the-g1t-plan), a |
| 178 | workspace's private packages may hold 500 MB and its public ones 10 GB, as |
| 179 | for [container images](/guides/containers/#storage-and-pull-limits). A |
| 180 | `.gem` is stored once, by its content. |
| 181 | |
| 182 | ## Errors |
| 183 | |
| 184 | | Error | Means | |
| 185 | | --- | --- | |
| 186 | | `401` | No credentials or key, or a wrong or expired token. For Bundler, set the source's credentials with `bundle config set`; for `gem install`, put them in the source's address; for `gem push`, give `GEM_HOST_API_KEY`. | |
| 187 | | `403` | Signed in, but your role or your token's scopes do not allow it, or the workspace is out of free package storage. The response says which. | |
| 188 | | `404` | No such gem or version, or a private one you cannot see. | |
| 189 | | `409` | That version is already pushed, or its name is taken by a gem named in another case. | |
| 190 | | `422` | The push was refused: not a `.gem`, or a name or version RubyGems would not take. The response says which. | |
| 191 | | `413` | The `.gem` is over 100 MB. | |