How to use Ruby CGI scripts on Uberspace - ruby

I want to use Ruby CGI scripts on an Uberspace 7, but ran into several issues with permissions and security settings, in particular when using gems. How do I install CGI scripts with custom gems?

First, note that Uberspace 7 runs on SELinux. This means that CGI script files in ~/html/ not only have to be executable but also need the correct SELinux context. In this case, the type must be httpd_sys_content_t.
You can view the SELinux context with ls -lZ:
$ ls -Z file1
-rw-rw-r-- user1 group1 unconfined_u:object_r:user_home_t:s0 file1
If some files have the wrong context, the context can be restored with the restorecon command, e.g. restorecon -R ~/html/.
The user installation directory for Ruby gems is ~/.gem/. On Uberspace, gem install installs into that directory by default:
$ cat /etc/gemrc
gem: --no-document --user-install
As the home directory cannot be accessed by the apache process, gems installed there cannot be executed from CGI scripts. You can install gems in /var/www/virtual/$USER/gem instead, create the directory with
$ mkdir /var/www/virtual/$USER/gem
You cannot use the --install-dir parameter for gem install directly as this conflicts with the default parameters mentioned above:
$ gem install mygem --install-dir /var/www/virtual/$USER/gem
ERROR: Use --install-dir or --user-install but not both
Instead, create ~/.gemrc with the following content to override the default parameters (replace <USERNAME> with your actual user name):
gem: --install-dir /var/www/virtual/<USERNAME>/gem
Now the installation of gems should work:
$ gem install mygem
To use the gems in CGI scripts, set the Gem.paths variable before requiring gems:
#!/usr/bin/ruby
Gem.paths = { 'GEM_PATH' => '/var/www/virtual/<USERNAME>/gem' }
require 'mygem'
(... rest of the script)
This is needed as we cannot modify the environment variables (i.e. set GEM_PATH) for the apache process.

Related

How to install gem via Makefile if it doesn't exist

I'm creating a Makefile for my project:
build:
sudo gem install sass
Any time I build it's asking me for my superuser password. If I remove sudo it will not install at all, but throw an error instead, as I don't have permissions to install a gem.
So I came up with an idea, that I want to check whether the gem already exists, and run installing command only when it doesn't.
So the question is how to perform this check inside Makefile.
From the command line you can see if a gem is installed with gem list <gemname>. This prints out a list of installed gems that match <gemname>:
$ gem list sass
*** LOCAL GEMS ***
sass (3.4.13, 3.4.1, 3.2.19)
sass-rails (5.0.1, 4.0.3)
The argument is actually a regex, so you can be more specific, checking e.g. only the Sass gem itself:
$ gem list \^sass\$
*** LOCAL GEMS ***
sass (3.4.13, 3.4.1, 3.2.19)
The -i flag to list makes it produce output more usable in scripts, printing true or false, and having a suitable exit status:
$ gem list \^sass\$ -i
true
$ echo $?
0
$ gem list \^notsass\$ -i
false
$ echo $?
1
You can combine this with Make’s conditionals, and the shell function (assuming GNU make) to check if a gem is installed from your makefile:
ifeq ($(shell gem list \^sass\$$ -i), false)
gem install sass
endif
(The extra $ is needed to prevent make trying to expand it as a variable.)
It's now traditional in the Ruby community to use Bundler to manage / install dependencies. This will install Gems without you having sudo privs, and also will keep different Ruby project's gems separate.
If you must install the gem raw, look into RVM or rbenv which both install Ruby and any future gems in your home directory. There' some logic you'd have to add to your Makefile to get it to use the new Ruby in your home folder (rbenv may make this easier than rvm, although Idon't know for sure)... but it's not hard.
One quick way to accomplish the task is writing an .rb script and execute it from a Makefile. The simplest script I came up with goes as follows:
#!/usr/bin/env ruby
if !Gem::Specification::find_all_by_name('sass').any?
exec("sudo gem install sass")
end
find_all_by_name is always returning an array and doesn't raise an error when it can't find anything (as find_by_name does).
Makefile:
default:
./install.rb
Make sure install.rb is executable:
chmod +x install.rb
Run it using make.

How to install a gem to the current folder

I want to use a Ruby gem locally (not install it for the entire machine) for use in a single script. I know how to install gems with Bundler with a Gemfile and bundle install. But for a simple script, this seems overkill to set up bundler.
Is there a way to install a gem to a subfolder of my script and use it, similar to the way npm installs Node.js packages in node_modules?
Here's what I have tried so far.
gem install -i ruby plist installs the plist gem in ruby/gems/plist-3.1.0
I tried to require it in my script extract.rb by doing require './ruby/gems/plist-3.1.0/lib/plist but that fails with require: cannot load such file: plist/generator (plist/generator.rb is required by lib/plist.rb).
Ruby 2.0 on OSX
You can bundle install to a different location with the --path option, for example:
bundle install --path vendor/bundle
Also see http://bundler.io/v1.1/bundle_install.html
If you don't want to involve Bundler, just install your gems locally as in your example and then set the GEM_PATH env in your script before your require, e.g.:
#!/usr/bin/env ruby
ROOT = File.expand_path('..', __FILE__)
ENV['GEM_PATH'] = File.join(ROOT, 'ruby')
# or to just append to
# ENV['GEM_PATH'] += ":#{ File.join(ROOT, 'ruby') }"
require 'plist'
assuming your script is in the same folder as the ruby folder (otherwise adjust the filepath accordingly).
You can do it by creating gemset for particular application. follow these steps for that -
$ rvm gemset create <gemset_name>
It will create a gemset for currently selected ruby version.
you can check currently selected ruby version by this command -
$ rvm list
Then navigate to your app directory by cd into it.
now execute this command -
$ rvm use #<gemset_name>
Now whenever you install any gem it will be installed in current gemset which is being used not for the entire machine.
Make sure - you run gem install bundler in newly created gemset so it will not raise error when you will run bundle install.

Is there a direct way to get ruby's gem path from rbenv?

I would like to get the path of the gem directory for the current ruby (under rbenv).
This gives me the bin dir:
rbenv which ruby
which I could chop around and suite my needs, but I'd like to see if there is something more direct.
I'm thinking that
rbenv prefix
is the best choice. This leaves me with something like
/Users/newalexandria/.rbenv/versions/1.9.3-p448
to which I append
/lib/ruby/gems/1.9.1/gems
I'm worried about the gems version.
Would like to see anything better.
rbenv exec gem environment returns information about your rubygems install.
Use it with grep to isolate the directory:
$ rbenv exec gem environment | grep INSTALLATION
> - INSTALLATION DIRECTORY: $HOME/.rbenv/versions/X.X.X-pX/lib/ruby/gems/X.X.X
there seems now to be a current command that provides support for the feature.
$ gem env home
/Users/newalexandria/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems

Execute without `bundle exec` via rubygems-bundler

I have a standard gem scaffold, and in inside the bin directory I have a simple executable (bin/do_things.rb):
#!/usr/bin/env ruby
require 'my_gem'
MyGem::doThings()
The gem hasn't been installed via gem install, so running bin/do_things.rb without bundle exec fails (my Gemfile has the gemspec line in it).
Is there a simple way to have rubygems-bundler execute my script in the bundler context? Should I just modify the $LOAD_PATH in my executable instead?
I could create a wrapper script to execute under Bundler as well, but I'd rather leverage rubygems-bundler somehow.
Or should I just type bundle exec?
try:
bundle exec bash -l
it will set you into bundler context - it's an extra shell so running exit will get you back to bundler less context.
Change the file permission
chmod a+x bin/do_things.rb
and resolve bin path, ignoring symlinks
require "pathname"
bin_file = Pathname.new(__FILE__).realpath
post, add self to libpath
$:.unshift File.expand_path("../../lib", bin_file)
Generating binstubs via bundle install --binstubs creates a wrapper script for all executables listed in my gemspec.
#!/usr/bin/env ruby
#
# This file was generated by Bundler.
#
# The application 'do_things.rb' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'pathname'
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
require 'rubygems'
require 'bundler/setup'
load Gem.bin_path('my_gem', 'do_things.rb')
However, by default, the binstubs path is bin, which conflicts with gem's executable path, and will overwrite files in bin.
Running bundle install --binstubs=SOME_DIR and then adding SOME_DIR to .gitignore seem to be the most maintainable way.
Then, I can simple execute SOME_DIR/do_things or any other project-specific executable I add down the line.

What determines where gems are installed?

I'm trying to set up a Sinatra app on my web host. I don't have sudo rights to install gems in the system-wide path, which is several subfolders beneath /usr/local, but I do have a gems folder in my app's directory.
Background
This reference gives the following definitions:
GEM_HOME - "Directory containing the master gem repository."
GEM_PATH - "Path list of directories containing gem repositories to be searched in addition to the GEM_HOME directory. The list should be delimited by the appropriate path separator (e.g. ‘:’ on Unix and ‘;’ on Windows)"
Initial settings on login
When I first ssh into this web host, echo $GEM_HOME and echo $GEM_PATH both produce an empty string, but gem list shows several gems.
Trying to change gem location
From the command line, I have set GEM_HOME like this:
GEM_HOME=$PWD/gems # 'gems' folder under present working directory
echo $GEM_HOME # correctly outputs the gem folder I specified
ls $GEM_HOME # shows gems folder contents, namely:
# bin/ cache/ docs/ gems/ specifications/
I also set GEM_PATH to the same folder.
After doing this, gem list still shows global gems rather than the gems in the specified folder, and gem install still tries to install to the global location.
What am I missing?
Use 'export'
Looks like export, as Tass showed, was the missing piece: it makes my local GEM_HOME variable a global one.
Here's what I've done:
export GEM_HOME=$PWD/gems # where to install and look for gems
export PATH=$PWD/gems/bin:$PATH # append the gems' binaries folder to
# the current path, so that I can call
# `bundle install` and have it find
# myapp/gems/bin/bundle
There is no manpage for gem, which doesn't make it easier. I assume GEM_PATH is where to look for the gems, and GEM_HOME is where to install them. Try
export GEM_HOME = "$GEM_PATH"
You could use Bundler as well. Bundler makes it very easy to manage Gem versions, even when sudo access is not possible. You create a file called Gemfile in the root of your application and put lines such as these:
gem "sinatra"
gem "some_other_gem_dependency"
gem "and_so_on_and_so_forth", ">= 1.0"
And then run bundle install --path /where/you/want/your/gems/stored which will install the gems to a path you have access to. You then put this in your config.ru:
require 'rubygems'
require 'bundler'
Bundler.require
require './your_app'
run YourApp
Check out http://gembundler.com/sinatra.html for more info.

Resources