Jack
Jack

Reputation: 2655

Installing Jekyll without root

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

[email protected] [~]# 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?

Upvotes: 9

Views: 3410

Answers (4)

Vineeth Bhaskaran
Vineeth Bhaskaran

Reputation: 2281

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

Upvotes: 1

S0AndS0
S0AndS0

Reputation: 920

... 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

  1. 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.

  1. 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.

Upvotes: 0

Jack
Jack

Reputation: 2655

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

Upvotes: 17

Kashyap
Kashyap

Reputation: 4796

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.

Upvotes: 0

Related Questions