I want to set up a jekyll blog on a shared server. When I try to install Jekyll I get "You don't have write permissions". How do I fix this without root or sudo?
More detail:
I have space on a shared server and don't have root access. I couldn't install Ruby, though the hosting company installed it upon my request.
When I try to install Jekyll I use
user#hosting.org [~]# gem install jekyll
and this is the response I get:
ERROR: While executing gem ... (Gem::FilePermissionError)
You don't have write permissions into the /usr/lib/ruby/gems/1.8 directory.
I have seen different suggestions for changing the GEMPATH which I have tried including
export GEM_PATH=/home/user/something
But even after doing so
gem env
still results in
GEM PATHS:
- /usr/lib/ruby/gems/1.8
- /home/user/.gem/ruby/1.8
Any tips? Is it possible to install jekyll without root or sudo priviliges or am I just making some rookie PATH error?
I didn't find the answer for a while. on the #jekyll IRC a user pointed me at the Arch wiki and I discovered that the thing is to force the install as a single user:
gem install jekyll --user-install
This worked for me in MAC
1.Place the gems in user's home folder.Add below commands in .bashrc or .zshrc
export GEM_HOME=$HOME/gems
export PATH=$HOME/gems/bin:$PATH
2.Use installation command
gem install jekyll bundler
3.Verify Installation
jekyll -v
Use the documentation for detailed reference
https://jekyllrb.com/docs/troubleshooting/#no-sudo
The reason for that is the default Ruby that gets shipped with Mac (I am assuming this, but this is true for some distributions of Linux as well) installs gems to a user folder that needs permissions to modify the contents. This is not a Ruby error to be precise.
That said, since Ruby 1.8.7 is not supported any more, you'd be better off avoiding using it and using one of the alternative ruby version managing tools like chruby or rvm or rbenv. (I'd vote for chruby btw). The documentation is pretty dense for all those. The authors are quite helpful in resolving issues if you do end up having one or more.
... am I just making some rookie PATH error?
Yes, I think so... I'm not sure why you're assigning GEM_PATH, I haven't needed to, and think ya perhaps wanted GEM_HOME instead. Though things may have changed since then and the current now that this'll be posted at.
TLDR
I usually write something such as...
## Ruby exports for user level gem & bundle installs
export GEM_HOME="${HOME}/.gem"
export PATH="${GEM_HOME}/bin:${PATH}"
... to somewhere like ~/.bash_aliases for each user that'll be authenticating to a server.
Then within any git-shell-commands script, for an authenticated user that makes use of Gems, source the above settings prior.
I want to set up a jekyll blog on a shared server. When I try to install Jekyll I get "You don't have write permissions". How do I fix this without root or sudo?
Might be worth checking out a project I've published a little while ago. It's been written and tested on Linux systems with Bash versions >= 4 if you sort out Mac feel free to make a PR. Otherwise, for shared servers, the least amount of fuss may be had by sticking with Xenial, from Ubuntu, or the freshest Raspberry flavored Debian.
Here's some snippets that should aid in automating an answer to your question...
/usr/local/etc/Jekyll_Admin/shared_functions/user_mods/jekyll_gem_bash_aliases.sh
#!/usr/bin/env bash
jekyll_gem_bash_aliases(){ ## jekyll_gem_bash_aliases <user>
local _user="${1:?No user name provided}"
local _home="$(awk -F':' -v _user="${_user}" '$0 ~ "^" _user ":" {print $6}' /etc/passwd)"
if [ -f "${_home}/.bash_aliases" ]; then
printf '%s/.bash_aliases already exists\n' "${_home}" >&2
return 1
fi
## Save new user path variable for Ruby executables
su --shell "$(which bash)" --command 'touch ${HOME}/.bash_aliases' --login "${_user}"
tee -a "${_home}/.bash_aliases" 1>/dev/null <<'EOF'
## Ruby exports for user level gem & bundle installs
export GEM_HOME="${HOME}/.gem"
export PATH="${GEM_HOME}/bin:${PATH}"
EOF
su --shell "$(which bash)" --command 'chmod u+x ${HOME}/.bash_aliases' --login "${_user}"
printf '## %s finished\n' "${FUNCNAME[0]}"
}
The above is used by one of three scripts that make use of sudo level permissions, specifically jekyll_usermod.sh... but don't get too caught-up with grokking all the contortions that I'm asking of Bash, because the moral of the above function's story is that it writes something like...
## Ruby exports for user level gem & bundle installs
export GEM_HOME="${HOME}/.gem"
export PATH="${GEM_HOME}/bin:${PATH}"
... to somewhere like /srv/bill/.bash_aliases which'll get sourced in git-shell-commands scripts and/or other shared functions for account setup like the following...
/usr/local/etc/Jekyll_Admin/shared_functions/user_mods/jekyll_user_install.sh
#!/usr/bin/env bash
jekyll_user_install(){ ## jekyll_user_install <user>
local _user="${1:?No user name provided}"
su --shell "$(which bash)" --login "${_user}" <<'EOF'
source "${HOME}/.bash_aliases"
mkdir -vp "${HOME}"/{git,www}
## Initialize Jekyll repo for user account
_old_PWD="${PWD}"
mkdir -vp "${HOME}/git/${USER}"
cd "${HOME}/git/${USER}"
git init .
git checkout -b gh-pages
_ruby_version="$(ruby --version)"
printf 'Ruby Version: %s\n' "${_ruby_version}"
_ruby_version="$(awk '{print $2}' <<<"${_ruby_version%.*}")"
_ruby_version_main="${_ruby_version%.*}"
_ruby_version_sub="${_ruby_version#*.}"
if [[ "${_ruby_version_main}" -ge '2' ]] && [[ "${_ruby_version_sub}" -ge '1' ]]; then
gem install bundler -v '< 2'
gem install jekyll -v '3.8.5'
bundle init
bundle install --path "${HOME}/.bundle/install"
bundle add jekyll-github-metadata github-pages
bundle exec jekyll new --force --skip-bundle "${HOME}/git/${USER}"
bundle install
else
echo 'Please see to installing Ruby verion >= 2.4' >&2
echo 'Hints may be found at, https://jekyllrb.com/docs/installation/' >&2
fi
git config receive.denyCurrentBranch updateInstead
cat >> "${HOME}/git/${USER}/.gitignore" <<EOL
# Ignore files and folders generated by Bundler
Bundler
vendor
.bundle
Gemfile.lock
EOL
git add --all
git -c user.name="${USER}" -c user.email="${USER}#${HOSTNAME}" commit -m "Added files from Bundler & Jekyll to git tracking"
cd "${_old_PWD}"
EOF
local _exit_status="${?}"
printf '## %s finished\n' "${FUNCNAME[0]}"
return "${_exit_status}"
}
Note, .bash_aliases is arbitrary as far as file naming, well so long as one is consistent, it could even be more explicit via something like .gems_aliases; end-users need not know what happens behind the curtains to make this magic happen in other words.
... which'll hopefully show one clear method of causing gem install someThing and related commands to search the user's installed packages first. Though in case another example is needed...
~/git-shell-commands/jekyll-init
#!/usr/bin/env bash
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
__SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's#^.* -> \(.*\)#\1#p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"
__NAME__="${__SOURCE__##*/}"
__AUTHOR__='S0AndS0'
__DESCRIPTION__='Initializes new Git repository with a gh-pages branch'
## Provides 'failure'
# source "${__DIR__}/shared_functions/failure"
# trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
## Provides: argument_parser <arg-array-reference> <acceptable-arg-reference>
source "${__DIR__}/shared_functions/arg_parser"
## Provides: git_add_commit <string>
source "${__DIR__}/shared_functions/git_shortcuts"
## Provides: __license__ <description> <author>
source "${__DIR__}/shared_functions/license"
usage(){
_message="${1}"
_repo_name="${_repo_name:-repository-name}"
cat <<EOF
## Usage
# ssh ${USER}#host-or-ip ${__NAME__} ${_git_args[#]:-$_repo_name}
#
# ${__DESCRIPTION__}
#
# --quite
# Git initializes quietly
#
# --shared
# Allow git push for group $(groups | awk '{print $1}')
#
# --template=<path>
# Template git repository that git init should pull from
#
# ${_repo_name}
# Name of repository to internalize or add Jekyll gh-pages branch to
#
## For detailed documentation of the above options.
## See: git help init
#
# --clean
# Remove non-git related files and directories from gh-pages branch prior to
# initializing Jekyll related files. This allows for files from previous branch
# to remain separate from files being tracked on the gh-pages branch.
#
# -l --license
# Shows script or project license then exits
#
# -h --help help
# Displays this message and exits
#
## The following options maybe used to modify the generated _config.yml file
#
# --title ${_title}
# --email ${_email}
# --twitter-username ${_twitter_username}
# --github-username ${_github_username}
EOF
if [ -n "${_message}" ] && [[ "${_message}" != '0' ]]; then
printf 'Error - %s\n' "${_message}" >&2
fi
}
_args=("${#:?# No arguments provided try: ${__NAME__} help}")
_valid_args=('--help|-h|help:bool'
'--license|-l|license:bool'
'--quiet:bool'
'--clean:bool'
'--shared:bool'
'--template:path'
'--title:print'
'--email:print'
'--twitter-username:posix'
'--github-username:posix'
'--repo-name:posix-nil')
argument_parser '_args' '_valid_args'
_exit_status="$?"
_git_args=()
if ((_quiet)); then _git_args+=('--quiet'); fi
if ((_shared)); then _git_args+=('--shared'); fi
if [ -n "${_template}" ]; then _git_args+=("--template='${_template}'"); fi
if [ -n "${_repo_name}" ]; then _git_args+=("${_repo_name}"); fi
## Set defaults for some variables if not already set
_github_username="${_github_username:-$USER}"
if [ -z "${_title}" ]; then
for _word in ${_repo_name//[-_]/ }; do
if [[ "${#_word}" -ge '4' ]]; then
_temp_title+=("${_word^}")
else
_temp_title+=("${_word}")
fi
done
_title="${_temp_title[#]}"
fi
_bundle_path="${HOME}/.bundle/install"
if ((_help)) || ((_exit_status)); then
usage "${_exit_status}"
exit "${_exit_status}"
elif ((_license)); then
__license__ "${__DESCRIPTION__}" "${__AUTHOR__}"
exit 0
fi
if [ -z "${_repo_name}" ]; then
usage 'missing repository name argument!'
exit "1"
fi
_git_path="${HOME}/git/${_repo_name:?No repository name provided}"
_old_PWD="${PWD}"
if [ -d "${_git_path}" ]; then cd "${_git_path}"; fi
_git_dir="$(git rev-parse --git-dir 2>/dev/null)"
if [[ "${_git_path}/${_git_dir}" == "${_git_path}/.git" ]]; then
printf '# Skipping git init, path already tracked by git: %s\n' "${_git_preexisting_dir}"
elif [[ "${_git_path}/${_git_dir}" == "${_git_path}/." ]]; then
echo '# Bare git repository detected, cannot install Jekyll to that right now'
exit 1
else
if [ -e "${HOME}/git-shell-commands/git-init" ]; then
"${HOME}/git-shell-commands/git-init" ${_git_args[#]}
else
cd "${HOME}/git" || exit 1
git init ${_git_args[#]}
fi
fi
cd "${_git_path}" || exit 1
_git_branches="$(git branch --list)"
_orig_branch="$(awk '/\*/{print $2}' <<<"${_git_branches}")"
_pages_branch="$(awk '/gh-pages/{print $2}' <<<"${_git_branches}")"
if [ -n "${_pages_branch}" ]; then
printf '# There is already a pages branch %s for repository %s\n' "${_pages_branch}" "${_repo_name}"
exit 1
fi
git_add_commit "Added files on ${_orig_branch} prior to installing Bundler & Jekyll to gh-pages branch"
git checkout -b gh-pages
if [[ "$(git config receive.denyCurrentBranch)" != 'updateInstead' ]]; then
git config receive.denyCurrentBranch updateInstead
fi
if ((_clean)); then
for _path in ${_git_path}/*; do
case "${_path}" in
*'.git') [[ -d "${_path}" ]] && continue ;;
*'.gitignore') [[ -f "${_path}" ]] && continue ;;
esac
git rm -rf "${_path}"
done
git_add_commit 'Cleaned gh-pages branch of files from parent branch'
fi
modify_config_yml(){
if ! [ -f "${_git_path}/_config.yml" ]; then
printf 'Error - no Jekyll config file found under %s\n' "${_git_path}" >&2
return 1
fi
if [ -n "${_title}" ]; then
sed -i "/title:/ { s#:[a-zA-Z 0-9]*#: ${_title}#; }" "${_git_path}/_config.yml"
fi
if [ -n "${_email}" ]; then
sed -i "/email:/ { s#:[a-zA-Z 0-9]*#: ${_email}#; }" "${_git_path}/_config.yml"
fi
if [ -n "${_twitter_username}" ]; then
sed -i "/_twitter_username:/ { s#:[a-zA-Z 0-9]*#: ${_twitter_username}#; }" "${_git_path}/_config.yml"
fi
if [ -n "${_github_username}" ]; then
sed -i "/github_username:/ { s#:[a-zA-Z 0-9]*#: ${_github_username}#; }" "${_git_path}/_config.yml"
fi
if [[ "${_repo_name}" != "${_github_username}" ]]; then
tee -a "${_git_path}/_config_baseurl.yml" 1>/dev/null <<EOF
# Use base URL to simulate GitHub pages behaviour
baseurl: "${_repo_name}"
EOF
fi
}
source "${HOME}/.bash_aliases"
bundle init || exit "${?}"
bundle install --path "${_bundle_path}"
bundle add jekyll
bundle exec jekyll new --force --skip-bundle "${_git_path}"
modify_config_yml
bundle install
cat >> "${_git_path}/.gitignore" <<EOF
# Ignore files and folders generated by Bundler
Bundler
vendor
.bundle
Gemfile.lock
EOF
git_add_commit 'Added files from Bundler & Jekyll to git tracking'
[[ "${_old_PWD}" == "${_git_path}" ]] || cd "${_old_PWD}"
printf '# Clone %s via: git clone %s#domain_or_ip:%s\n' "${_repo_name}" "${USER}" "${_git_path//${HOME}\//}"
printf '# %s finished\n' "${__NAME__}"
... which also shows how to bundle install someThing to somewhere.
Good luck with the publishing and perhaps comment if ya get stuck.
Related
Is it possible to start the mc-wrapper with sudo and still use the last selected directory on the console when quitting sudo mc (requirement number 4)? My default alias looks like this.
alias mc='EDITOR="${EDITOR-mcedit}" . /usr/lib/mc/mc-wrapper.sh'
Some errors (for the Googlers)
sudo: mc: command not found
sudo: .: command not found # "." == "source"
My requirements
Ubuntu 18.04.1.
The alias should work with and without sudo call.
If possible, a single alias for mc in /etc/bash.bashrc for all users.
The directory you changed to with sudo mc should be "preserved" after closing the Midnight Commander. This means that you will not be in the same directory as you started sudo mc (provided it is not the same directory).
Optional requirements
See if the alias was started with super powers.
See if the alias was started with sudo.
If the alias mc was started without super powers or sudo, ask if the program mc should still be started with sudo rights.
If the question is answered No, use my default alias.
In all other cases, the first four requirements should be met.
The editor (e.g. mcedit or vi) within mc should be selectable via another alias like mcvi (for vi) without code duplication.
Arguments should be passed on to the program, like sudo mc /opt/ /mnt/
Here's one hacky solution (tested, but the last two optional requirements are still missing).
/etc/bash.bashrc
alias sudo='sudo ' # fixes "sudo: mc: command not found" (breaks with an argument: sudo -H ll)
# sudo apt update && sudo apt install dialog mc pwgen
#
# Start the alias with a "real" command (instead of a shell keyword like "if") so that sudo is not confused.
# This first command (env) will eat sudo and all arguments! Even the following file redirection including the angle bracket is already executed without sudo.
alias mc='env > "/tmp/mc.env.$(whoami).$$"
MC_USER="$(whoami)"
MC_ENV_FILE="/tmp/mc.env.${MC_USER}.$$"
# cat "${MC_ENV_FILE}"
if [ "$(cat "${MC_ENV_FILE}" | grep "$(id -nu 0)" | wc -l)" -gt "3" ]; then
# echo "This alias was called with super powers."
MC_ROOT="root"
fi
if [ "$(cat "${MC_ENV_FILE}" | grep "^SUDO_" | wc -l)" -gt "3" ]; then
# echo "This alias was called with sudo (possibly sudo -i or sudo -s was entered before)."
MC_SUDO="sudo"
fi
if [ "${MC_ROOT}" == "root" ] || [ "${MC_SUDO}" == "sudo" ]; then
MC_DIALOG="0"
else
# echo "This alias was called as normal user."
dialog --cr-wrap --defaultno --title "sudo mc" --yesno "Do you want super powers (sudo/root)?\n\n(Alternatively you can use \"sudo mc\" directly next time.)\n\nAgain: Do you want super powers (sudo/root)?" 9 64
MC_DIALOG="$?"
tput reset
fi
if [ "${MC_DIALOG}" != "0" ]; then
# echo "No, do not use sudo and stay normal user."
# echo "Standard wrapper (without arguments)..."
EDITOR="${EDITOR-mcedit}" . /usr/lib/mc/mc-wrapper.sh # does not work with sudo because "." is not a program like "ls" or "grep"!
else
# echo "Yes, exec those decisive commands with super powers."
# echo "Custom wrapper (also without arguments)..."
MC_PWD_FILE_DIRNAME="${TMPDIR-/tmp}/mc-${MC_USER}/"
MC_PWD_FILE="${MC_PWD_FILE_DIRNAME}mc.pwd.$$.$(pwgen 13 1)"
sudo mkdir -p "$MC_PWD_FILE_DIRNAME"
sudo chown "$(sudo whoami)":"$(sudo whoami)" "$MC_PWD_FILE_DIRNAME"
sudo chmod 0700 "$MC_PWD_FILE_DIRNAME"
sudo EDITOR="${EDITOR-mcedit}" /usr/bin/mc -P "$MC_PWD_FILE"
sudo chown -R "$MC_USER":"$MC_USER" "$MC_PWD_FILE_DIRNAME"
if test -r "$MC_PWD_FILE"; then
MC_PWD=$(cat "$MC_PWD_FILE")
if test -n "$MC_PWD" && test -d "$MC_PWD"; then
cd "$MC_PWD"
fi
unset MC_PWD
fi
rm -f "$MC_PWD_FILE"
unset MC_PWD_FILE
unset MC_PWD_FILE_DIRNAME
fi
unset MC_DIALOG
unset MC_SUDO
unset MC_ROOT
rm -f "${MC_ENV_FILE}"
unset MC_ENV_FILE
unset MC_USER
# This terminating line break is required:
'
I didn't manage to use a function mcwrapper (and $(declare -f mcwrapper)) and I don't think it's that easy either!?
I've created a shell script to install rbenv onto a fresh virtual machine, everyone seems to be working fine, however, once and a while it seems that rbenv fails to install. I'd like to add some extra error checking throughout the way to ensure that the install happens, and if something failed, re-start the install.
Some notes:
This is a small part of a larger script
This is all done through whiptail dialog
I'm redirecting the output to an events.log file >&$log_fd...
This is what I currently have:
set_rbenv(){
# install rbenv
RUBY_VERSION="2.1.4"
RBENV_ROOT="/root/.rbenv"
PROFILE="/root/.bashrc"
# check if rbenv is installed
if [[ ! `which rbenv` ]]; then
if [[ ! -d "$RBENV_ROOT" ]]; then
echo "Cloning rbenv..." >&$log_fd 2>&1
git clone https://github.com/rbenv/rbenv.git $RBENV_ROOT >&$log_fd 2>&1
fi
if [[ ! -d "$RBENV_ROOT/plugins/ruby-build" ]]; then
git clone https://github.com/rbenv/ruby-build.git $RBENV_ROOT/plugins/ruby-build >&$log_fd 2>&1
fi
if [[ ! `grep -e '/.rbenv\/bin' $PROFILE` ]]; then
echo "rbenv not in PATH ... adding..." >&$log_fd 2>&1
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> $PROFILE 2>&1
fi
if [[ ! `grep -e '^\eval "$(rbenv init -)"' $PROFILE` ]]; then
echo "rbenv init not in .bashrc... adding..." >&$log_fd 2>&1
echo 'eval "$(rbenv init -)"' >> $PROFILE 2>&1
fi
. $PROFILE
echo "Finished Installing rbenv..." >&$log_fd 2>&1
else
echo "rbenv already installed... skipping!" >&$log_fd 2>&1
fi
if [ ! -d "$RBENV_ROOT/versions" ]; then
echo "$RBENV_ROOT/versions doesn't exist ... creating..."
mkdir $RBENV_ROOT/versions >&$log_fd 2>&1
fi
if [ ! -d "$RBENV_ROOT/versions/$RUBY_VERSION" ]; then
echo "$RBENV_ROOT/versions/$RUBY_VERSION doesn't exist ... creating..." >&$log_fd 2>&1
mkdir $RBENV_ROOT/versions/$RBENV_VERSION >&$log_fd 2>&1
fi
# check if ruby version is installed
if [[ ! `rbenv version | grep $RUBY_VERSION` ]]; then
echo "installing ruby 2.1.4" >&$log_fd 2>&1
rbenv install $RUBY_VERSION >&$log_fd 2>&1
rbenv global $RUBY_VERSION >&$log_fd 2>&1
if [ ! -d "$RBENV_ROOT/versions/$RUBY_VERSION/etc" ]; then
echo "$RBENV_ROOT/versions/$RUBY_VERSION/etc doesn't exist ... creating..." >&$log_fd 2>&1
mkdir $RBENV_ROOT/versions/$RUBY_VERSION/etc >&$log_fd 2>&1
fi
else
echo "ruby $RUBY_VERSION already exists... skipping!" >&$log_fd 2>&1
fi
(( STATUS += 35 ))
echo $STATUS
}
While the install itself can fail at anytime on the new VM, I'm screwed if rbenv installs, but nothing else does (like ruby-build) or the $PATH, etc.
Would it make more sense to just encase every command in it's own if statement outside of the initial if which rbenv, or is there a more robust way to ensure the install completes successfully?
You can actually perform this task more easily using configuration management tools such as Ansible, puppet or Chef. Instead of trying to specify "how" to check and use endless IF statement, you can specify "what" to check and use existing framework to do so.
For example, in Ansible you can specify to update cache & install latest version of foo using apt module:
- hosts: all
remote_user: root
tasks:
- name: Install the package foo
apt:
name: foo
state: present
update_cache: yes
More information
I'm running a shell script via capistrano to install rvm and ruby. When running
rvm install ruby-${RUBY_VERSION} 2>&1 > ../log/ruby_install.log
in my script, all output seems to be going to the log file, except for the scrollbar output. that output is being sent back to capistrano and it's flooding the output, and looks horrible.
Is there any way I can hide the progress during the command?
I tried running
alias curl="curl --silent"
before the command, but it doesn't work at all, so I guess the install is happening via some other means.
Try:
gem install rvm-capistrano -v ">=1.3.0.rc11"
It contains code to make curl silent
The answer from #mpapis lead me to the following solution:
# Rename .curlrc if present
if [[ -f $HOME/.curlrc ]]; then
echo "Backing up .curlrc"
mv $HOME/.curlrc $HOME/.curlrc~
fi
# Create a temporary .curlrc configuration file, this prevents curl from flooding the Capistrano output
{
echo "insecure"
echo "silent" # Hide verbose output, it floods the capistrano output
echo "show-error"
} > $HOME/.curlrc
I added the above snippet to my bash script, and at the end, I just restored .curlrc to it's previous state:
rm $HOME/.curlrc
if [[ -f $HOME/.curlrc~ ]]; then
mv $HOME/.curlrc~ $HOME/.curlrc
fi
This is modified from rvm-capistrano, check it out on the original Github Repository.
I installed Ruby 1.9.3-p286 with rbenv. Now, after installing a newer version (p327), obviously, it doesn't know anything about the GEMs installed with the previous version.
Is it possible to copy Gems from that version to the newer one, so that it won't be needed to download them all again?
You can copy the gems/ and bin/ folders, but this will lead to problems. The files in bin/ have hardcoded paths in them.
I'd recommend reinstalling them, which would be as easy as this:
$ rbenv local 1.9.3-p286
$ gem list | cut -d" " -f1 > my-gems
$ rbenv local 1.9.3-p327
$ gem install $(cat my-gems)
I've been looking at this specifically from the perspective of upgrading and reinstalling without downloading. It's not trivial, and I recommend you do some cleanup of your gems to minimize the amount of processing/installation that needs to be done (e.g., I had five versions of ZenTest installed; I did 'gem cleanup ZenTest' before doing this). Be careful with 'gem cleanup', though, as it removes all but the LAST version: if you need to support an older version of Rails, manually clean up the versions you don't need.
I called this script 'migrate-gems.sh':
#! /bin/bash
if [ ${#} -ne 2 ]; then
echo >&2 Usage: $(basename ${0}) old-version new-version
exit 1
fi
home_path=$(cd ~; pwd -P)
old_version=${1}
new_version=${2}
rbenv shell ${old_version}
declare -a old_gem_paths old_gems
old_gem_paths=($(gem env gempath | sed -e 's/:/ /'))
rbenv shell ${new_version}
for ogp in "${old_gem_paths[#]}"; do
case "${ogp}" in
${home_path}/.gem/ruby*|*/.gem/ruby*)
# Skip ~/.gem/ruby.
continue
;;
esac
for old_gem in $(ls -1 ${ogp}/cache/*.gem); do
gem install --local --ignore-dependencies ${ogp}/cache/${old_gem}
done
done
There are three pieces that make this work:
gem env gempath contains the paths (:-separated) where gems are installed. Because
gems are shared in ~/.gem/ruby, I skip this one.
gem install accepts --local, which forces no network connections.
gem install accepts --ignore-dependencies, which disables dependency checking.
I had a fairly large list of gems to move over today and I didn't want to download from rubygems.org (plus, I needed older versions), so I whipped this up fairly quickly.
For posterity, I wrote rbenv-copy-gems.sh to help me do this. It doesn't meet the criteria of this question (it installs from the Internet, not locally), but it's been useful for me as I upgrade or install new version of Ruby via rbenv install.
Current version pasted below for reference, but I keep the gist up to date as I make improvements.
#!/bin/bash
# copy is a misnomer; it's actually LIST + INSTALL
# --from 2.2.1 [--to other-version-else-whatever-is-currently-set]
#
# TODO: install only most-recent version that's installed in FROM
# TODO: use gem names only from FROM, install latest available version (might be more recent than in FROM)
# TODO: pass arguments to gem command (e.g. --no-document)
CURRENT_VERSION=`rbenv version | cut -d' ' -f1`
GEM_LIST_ARGS="--local"
while [[ $# -gt 0 ]]; do
option="$1"
case $option in
--from)
FROM="$2"
shift
;;
--to)
TO="$2"
shift
;;
esac
shift # past argument or value
done
if [ -z "${FROM}" ]; then
FROM="${CURRENT_VERSION}"
fi
if [ -z "${TO}" ]; then
TO="${CURRENT_VERSION}"
fi
echo ">>> Install gems from v${FROM} to v${TO}"
# Get gems and versions, reformat to GEMNAME:version[,version[...]]
gems=(`RBENV_VERSION=${FROM} gem list ${GEM_LIST_ARGS} | sed -e 's/[\(\)]//g' -e 's/, /,/g' -e 's/ /:/'`)
for geminfo in "${gems[#]}"; do
gem=`echo $geminfo | cut -d: -f1`
versions=(`echo $geminfo | sed -e 's/^.*://' -e 's/,/ /g'`)
for version in "${versions[#]}"; do
installed=`RBENV_VERSION=${TO} gem query -i $gem -v $version`
if [ "${installed}" == "false" ]; then
echo ">>> Installing ${gem} ${version}:"
RBENV_VERSION=${TO} gem install $gem -v $version
else
echo ">>> ${gem} ${version} already installed"
fi
echo ""
done
done
Some while ago, our .rvmrc file looked like this (pretty default):
#!/usr/bin/env bash
# This is an RVM Project .rvmrc file, used to automatically load the ruby
# development environment upon cd'ing into the directory
# First we specify our desired <ruby>[#<gemset>], the #gemset name is optional,
# Only full ruby name is supported here, for short names use:
# echo "rvm use 1.9.3" > .rvmrc
#environment_id="ruby-1.9.3-p0#iq"
environment_id="ruby-1.9.3-p0"
# Uncomment the following lines if you want to verify rvm version per project
# rvmrc_rvm_version="1.14.1 (master)" # 1.10.1 seams as a safe start
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
# return 1
# }
# First we attempt to load the desired environment directly from the environment
# file. This is very fast and efficient compared to running through the entire
# CLI and selector. If you want feedback on which environment was used then
# insert the word 'use' after --create as this triggers verbose mode.
if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
then
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
[[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
\. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
else
# If the environment file has not yet been created, use the RVM CLI to select.
rvm --create "$environment_id" || {
echo "Failed to create RVM environment '${environment_id}'."
return 1
}
fi
# If you use bundler, this might be useful to you:
# if [[ -s Gemfile ]] && {
# ! builtin command -v bundle >/dev/null ||
# builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null
# }
# then
# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
# gem install bundler
# fi
# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
# then
# bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete'
# fi
To be used with RubyMine, a collegue changed it to the following
rvm ruby-1.9.3-p0 --create
The bad is, that my TextMate now automatically switches to Ruby 1.8.7 when executing a file from within the folder, so it doesn't work with this anymore (but before, it did by setting the TM_RUBY variable to /Users/josh/.rvm/bin/rvm-auto-ruby). As I'm the only TextMate user, I'm on my own now to figure out what's wrong. Anybody has an idea?
When I remove the .rvmrc file, then it loads the default RVM ruby version - if it's there, it loads the system ruby (1.8.7).
I also tried the wrapper approach, described on the rvm page, but this also loads the system ruby (1.8.7).
Thanks for any help! Josh
check the docs at RVM Site Texmate integration