#!/bin/bash # # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # echo -e "***********************************************************************" echo -e "* \e[31mWARNING:\e[0m *" echo -e "* This program will try to install automatically Decidim and all *" echo -e "* related software. This includes Nginx, Passenger, Ruby and others. *" echo -e "* \e[33mUSE IT ONLY IN A FRESHLY INSTALLED UBUNTU 18.04 or 20.04 SYSTEM\e[0m *" echo -e "* No guarantee whatsoever that it won't break your system! *" echo -e "* *" echo -e "* (c) Ivan Vergés *" echo -e "* https://github.com/Platoniq/decidim-install *" echo -e "* *" echo -e "***********************************************************************" ######################################################## # Config vars & default values (use -h to view options) ######################################################## RUBY_VERSION="2.7.4" DECIDIM_VERSION="0.26" BUNDLER_VERSION="2.2.18" RAILS_VERSION="6.0.4" VERBOSE= CONFIRM=1 STEPS=("check" "prepare" "rbenv" "gems" "decidim" "postgres" "create" "servers") # default environment to be configured ENVIRONMENT="production" ################### # Function library ################### # exit on fail (trap on some cases applies) set -e info() { echo -e "$1" } yellow() { echo -e "\e[33m$1\e[0m" } green() { echo -e "\e[32m$1\e[0m" } red() { echo -e "\e[31m$1\e[0m" } exit_help() { info "\nUsage:" info " $0 [OPTIONS] [FOLDER]\n" info "Installs Decidim into FOLDER and all necessary dependencies in Ubuntu 18.04\n" info "This script tries to be idempotent meaning that it can be run repeatedly" info "without breaking things or changing values in already configured steps\n" info "OPTIONS:" info " -h Show this help" info " -f Do not ask for confirmation to run the script" info " -v Be verbose (when possible)" info " -r [ver] Specify ruby version (default is $RUBY_VERSION)" info " -e [env] Specify rails environment (default is $ENVIRONMENT)" info " -s [step] Skip the step specified. Multiple steps can be" info " specified with several -s options" info " -o [step] Execute only the step specified. Multiple steps can be" info " specified with several -o options" info " -u [email] Specify Decidim system admin email" info " -p [pass] Specify Decidim system admin password" info " -c Install in Capistrano mode. releases and current will be used as suffix for the specified directory" info "\nValid steps are (in order of execution):" info " check Checks if we are using Ubuntu 18.04" info " prepare Updates system, configure timezone" info " rbenv Installs ruby through rbenv" info " gems Installs Ruby gems bundler and decidim" info " decidim Installs Decidim into FOLDER and generates database credentials if necessary" info " postgres Installs PostgreSQL and creates the user using the generated credentials" info " create Creates the database and the first system admin user" info " servers Configures Nginx, Passenger and ActiveJob" trap - EXIT exit } # Disables traps and exits immediately # Used to trap INT and TERM signals abort() { red "Aborted by the user!" trap - EXIT exit } # Checks the last command result on exit # Used to trap the EXIT signal of this script cleanup() { rv=$? if [ "$rv" -ne 0 ]; then red "Something went wrong! Aborting!" exit $rv else green "Finished successfully!" fi } step_check() { green "Checking current system..." if [ "$EUID" -eq 0 ]; then red "Please do not run this script as root" info "User a normal user with sudo permissions" info "sudo password will be asked when necessary" exit 1 fi if [ $(awk -F= '/^ID=/{print $2}' /etc/os-release) != "ubuntu" ]; then red "Not an ubuntu system!" cat /etc/os-release exit 1 fi version=$(awk -F= '/^VERSION_ID=/{print $2}' /etc/os-release) if [ "$version" != '"18.04"' ] && [ "$version" != '"20.04"' ]; then red "Only Ubuntu 18.04 or 20.04 are supported!" awk -F= '/^VERSION_ID=/{print $2}' /etc/os-release exit 1 fi # TODO: check for system memory } step_prepare() { green "Updating system" sudo apt-get update sudo apt-get -y upgrade sudo apt-get -y autoremove green "Configuring timezone" sudo dpkg-reconfigure tzdata green "Installing necessary software" sudo apt-get -y install autoconf bison build-essential libssl-dev libyaml-dev \ libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev } init_rbenv() { export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)" } cd_folder(){ if [ -z "$FOLDER" ]; then yellow "Please specify a folder to install Decidim" info "Runt $0 with -h to view options for this script" exit 0 fi if [ -z "$CAPISTRANO" ]; then INSTALL_FOLDER=$FULLFOLDER else INSTALL_FOLDER=$FULLFOLDER/current fi if [ -d "$INSTALL_FOLDER" ]; then green "changing to working folder [$INSTALL_FOLDER] from [$PWD]" cd $INSTALL_FOLDER else red "Couldn't change to working folder! [$INSTALL_FOLDER]" fi } step_rbenv() { # pause EXIT trap trap - EXIT info "Installing rbenv" if [ -d "$HOME/.rbenv" ]; then yellow "$HOME/.rbenv already exists!" else info "Installing rbenv from GIT source" git clone https://github.com/rbenv/rbenv.git $HOME/.rbenv fi if grep -Fxq 'export PATH="$HOME/.rbenv/bin:$PATH"' "$HOME/.bashrc" ; then yellow "$HOME/.rbenv/bin already in PATH" else info "Installing $HOME/.rbenv/bin in PATH" echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> "$HOME/.bashrc" fi if grep -Fxq 'eval "$(rbenv init -)"' "$HOME/.bashrc" ; then yellow "rbenv init already in bashrc" else info "Installing rbenv init in bashrc" echo 'eval "$(rbenv init -)"' >> "$HOME/.bashrc" fi init_rbenv if rbenv version; then green "rbenv successfully installed" else red "Something went wrong installing rbenv." red "rbenv does not appear to be a bash function" info "You might want to perform this step manually" type rbenv exit 1 fi # resume EXIT trap trap cleanup EXIT if [ -d "$HOME/.rbenv/plugins/ruby-build" ]; then yellow "$HOME/.rbenv/plugins/ruby-build already exists!" else info "Installing ruby-build from GIT source" git clone https://github.com/rbenv/ruby-build.git $HOME/.rbenv/plugins/ruby-build fi if rbenv install -l | grep -Fq "$RUBY_VERSION"; then green "Ruby $RUBY_VERSION rbenv available for installation" fi if [ $(rbenv global) == "$RUBY_VERSION" ]; then yellow "Ruby $RUBY_VERSION already installed" else info "Installing ruby $RUBY_VERSION, please be patient, it's going to be a while..." rbenv install "$RUBY_VERSION" -f $VERBOSE rbenv global "$RUBY_VERSION" fi if [[ $(ruby -v) == "ruby $RUBY_VERSION"* ]]; then green "$(ruby -v) installed successfully" info "It is recommended to logout and login again to activate .bashrc" fi } step_gems() { info "installing Yarn" curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt update sudo apt install --no-install-recommends yarn info "installing Node 16" curl -sL https://deb.nodesource.com/setup_16.x -o setup_16.sh chmod +x setup_16.sh sudo ./setup_16.sh info "Installing generator dependencies" sudo apt-get install -y nodejs imagemagick libpq-dev libicu-dev whereis node node --version init_rbenv info "Installing bundler" if [ -f "$HOME/.gemrc" ] ; then yellow "$HOME/.gemrc already created" else info "Creating $HOME/.gemrc" echo "gem: --no-document" > $HOME/.gemrc fi info "Installing bundler" gem install bundler --version $BUNDLER_VERSION if [[ $(gem env home) == *".rbenv/versions/$RUBY_VERSION/lib/ruby/gems/"* ]]; then green "Gems environment installed successfully" else red "gem home failed! $(gem env home)!" exit 1 fi info "Installing Rails, version $RAILS_VERSION" gem install rails --version $RAILS_VERSION # Version 0.25 had a bug and do not limit the version o rails to 6.0 in the generator # Therefore, if rails 6.1 is installed it will fail set +e gem list -e rails --versions | grep 6.1 -q if [ "$?" -eq 0 ]; then red "Rails 6.1 is installed. Please uninstall this version before using this script" gem list -e rails exit 1 fi set -e info "Installing Decidim gem" gem install decidim -v $DECIDIM_VERSION } FOLDER= CONF_SECRET= CONF_DATABASE= CONF_DB_USER=decidim_app CONF_DB_PASS=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo '') CONF_DB_HOST=localhost CONF_DB_NAME=decidim_prod DECIDIM_EMAIL= DECIDIM_PASS= CAPISTRANO= step_decidim() { if [ -z "$FOLDER" ]; then yellow "Please specify a folder to install Decidim" info "Runt $0 with -h to view options for this script" exit 0 fi init_rbenv if [ -z "$CAPISTRANO" ]; then INSTALL_FOLDER=$FOLDER else INSTALL_FOLDER=$FOLDER/releases/initial fi green "Installing Decidim in $INSTALL_FOLDER" if [ -d "$INSTALL_FOLDER" ]; then yellow "$INSTALL_FOLDER already exists, trying to install gems anyway" else decidim "$INSTALL_FOLDER" sed -i 's/ config.load_defaults 6.1/ config.load_defaults 6.0/' $INSTALL_FOLDER/config/application.rb fi if [ ! -z "$CAPISTRANO" ]; then green "Applying Capistrano enabled modifications..." info "Creating Capistrano directories" mkdir -p $FOLDER/shared/config mkdir -p $FOLDER/shared/log mkdir -p $FOLDER/shared/public/uploads if [ ! -L "$FOLDER/current" ]; then info "Symlink to Capistrano current version" ln -s releases/initial $FOLDER/current else yellow "Capistrano current version already linked" fi if [ ! -L "$INSTALL_FOLDER/config/application.yml" ]; then info "Creating application.yml file and Symlink to shared folder" touch "$FOLDER/shared/config/application.yml" ln -s $(realpath $FOLDER/shared/config/application.yml) "$INSTALL_FOLDER/config/application.yml" else yellow "application.yml is already a Symlink" fi if [ ! -L "$INSTALL_FOLDER/log" ]; then info "Moving logs to shared folder and Symlink it" mv "$INSTALL_FOLDER/log" "$FOLDER/shared/" ln -s $(realpath $FOLDER/shared/log) "$INSTALL_FOLDER/log" else yellow "log is already a Symlink" fi if [ ! -L "$INSTALL_FOLDER/public/uploads" ]; then info "Moving uploads to shared folder and Symlink it" if [ -d "$INSTALL_FOLDER/public/uploads" ]; then mv "$INSTALL_FOLDER/public/uploads" "$FOLDER/shared/public/" else mkdir -p "$INSTALL_FOLDER/public/uploads" fi ln -s $(realpath $FOLDER/shared/public/uploads) "$INSTALL_FOLDER/public/uploads" else yellow "uploads is already a Symlink" fi fi cd_folder if grep -FA1 'BUNDLED WITH' Gemfile.lock | grep -Fq "1.17.3" ; then yellow "Removing current Gemfile.lock file to use a more modern bundler" rm -f Gemfile.lock fi if grep -Fq 'gem "figaro"' Gemfile ; then info "Gem figaro already installed" else bundle add figaro --skip-install fi if grep -Fq 'gem "passenger"' Gemfile ; then info "Gem passenger already installed" else bundle add passenger --group $ENVIRONMENT --skip-install fi if grep -Fq 'gem "delayed_job_active_record"' Gemfile ; then info "Gem delayed_job_active_record already installed" else bundle add delayed_job_active_record --group $ENVIRONMENT --skip-install fi if grep -Fq 'gem "daemons"' Gemfile ; then info "Gem daemons already installed" else bundle add daemons --group $ENVIRONMENT --skip-install fi if grep -Fq 'gem "whenever"' Gemfile ; then info "Gem whenever already installed" else echo 'gem "whenever", require: false' >> Gemfile fi if [ ! -z "$CAPISTRANO" ]; then if grep -Fq 'gem "capistrano"' Gemfile ; then info "Gem capistrano already installed" else bundle add capistrano --group development --skip-install bundle add capistrano-rbenv --group development --skip-install bundle add capistrano-bundler --group development --skip-install bundle add capistrano-passenger --group development --skip-install bundle add capistrano-rails --group development --skip-install fi fi bundle install if [ -f "./config/schedule.rb" ]; then yellow "config/schedule.rb already present" else cat > ./config/schedule.rb <> ./.gitignore echo "/config/application.yml" >> ./.gitignore fi if ! grep -Fq 'SECRET_KEY_BASE:' ./config/application.yml ; then echo "SECRET_KEY_BASE: $(rake secret)" >> ./config/application.yml fi CONF_SECRET=$(awk '/SECRET_KEY_BASE\:/{print $2}' config/application.yml) if ! grep -Fq 'DATABASE_URL:' ./config/application.yml ; then echo "DATABASE_URL: postgres://$CONF_DB_USER:$CONF_DB_PASS@$CONF_DB_HOST/$CONF_DB_NAME" >> ./config/application.yml fi if grep -Fq '# config.force_ssl = true' ./config/initializers/decidim.rb ; then red "Disabling SSL by default!!!" red "NOTE: you should configure SSL in Nginx and then reenable 'config.force_ssl = true' in the file 'config/initializers/decidim.rb' again" yellow "You may follow this instructions for that: https://certbot.eff.org/lets-encrypt/snap-nginx" sed -i 's/# config.force_ssl = true/config.force_ssl = false/' ./config/initializers/decidim.rb fi } get_conf_vars() { cd_folder init_rbenv CONF_DATABASE=$(awk '/DATABASE_URL:/{print $2}' config/application.yml) re="postgres\:\/\/(.+):(.+)@(.+)/(.+)" if [[ "$CONF_DATABASE" =~ $re ]]; then CONF_DB_USER="${BASH_REMATCH[1]}"; CONF_DB_PASS="${BASH_REMATCH[2]}"; CONF_DB_HOST="${BASH_REMATCH[3]}"; CONF_DB_NAME="${BASH_REMATCH[4]}"; fi if [ -z "$CONF_DB_USER" ]; then red "Couldn't extract database user from config/application.yml!" exit 1 fi if [ -z "$CONF_DB_PASS" ]; then red "Couldn't extract database password from config/application.yml!" exit 1 fi if [ -z "$CONF_DB_HOST" ]; then red "Couldn't extract database host from config/application.yml!" exit 1 fi if [ -z "$CONF_DB_NAME" ]; then red "Couldn't extract database name from config/application.yml!" exit 1 fi } step_postgres() { get_conf_vars green "Installing PostgreSQL" sudo apt-get -y install postgresql echo "Starting PostgreSQL" sudo systemctl start postgresql.service if sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='$CONF_DB_USER'" | grep -q 1 ; then yellow "User $CONF_DB_USER already exists in postgresql" else info "Creating user $CONF_DB_USER" sudo -u postgres psql -c "CREATE USER $CONF_DB_USER WITH SUPERUSER CREATEDB NOCREATEROLE PASSWORD '$CONF_DB_PASS'" fi } step_create(){ get_conf_vars green "Database creation and migration" bin/rails db:create RAILS_ENV=$ENVIRONMENT bin/rails db:migrate RAILS_ENV=$ENVIRONMENT info "Ensure yarn is working" yarn add rails-ujs yarn install info "Fixing config/application.rb" yellow "This shouldn't be necessary but there was a bug in 0.25 version https://github.com/decidim/decidim/issues/8395" if grep -Fq 'action_cable/engine' ./config/application.rb ; then yellow "require action_cable already done" else green "adding require action_cable" sed -i 's/require "decidim\/rails"/require "decidim\/rails"\nrequire "action_cable\/engine"/' config/application.rb fi if grep -Fq 'Rails.autoloaders' ./config/application.rb ; then yellow "Autoloaders ignore already done" else green "adding autoloaders ignore" echo 'Rails.autoloaders.main.ignore(Gem::Specification.find_by_name("decidim-core").gem_dir + "/app/packs")' >> config/application.rb fi if [ "production" == "$ENVIRONMENT" ]; then green "Asset compiling in production mode" bin/rails assets:precompile RAILS_ENV=$ENVIRONMENT else yellow "Skipping asset compiling in $ENVIRONMENT mode" fi local email="$DECIDIM_EMAIL" local pass="$DECIDIM_PASS" if [ -z "$email" ]; then read -p "Introduce your system admin email: " email green "Using email [$email]" else yellow "Using email [$email] from options" fi if [ -z "$pass" ]; then read -p "Introduce your system admin password: " pass else yellow "Using password from options" fi info "Checking availability..." if $(bin/rails runner -e $ENVIRONMENT "puts Decidim::System::Admin.exists?(email: '$email')") == "true"; then yellow "System admin with email [$email] already exists!" else info "Creating system admin with email [$email]" bin/rails runner -e $ENVIRONMENT "Decidim::System::Admin.new(email: '$email', password: '$pass', password_confirmation: '$pass').save!" fi } step_servers(){ if [ "production" != "$ENVIRONMENT" ]; then red "servers step is only available in production mode" exit fi cd_folder init_rbenv green "Installing Nginx" sudo apt-get -y install nginx if [ -f /etc/apt/sources.list.d/passenger.list ]; then yellow "Passenger repositories already installed" else green "Installing Passenger repositories" sudo apt-get install -y dirmngr gnupg sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7 sudo apt-get install -y apt-transport-https ca-certificates sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger $(lsb_release -cs) main > /etc/apt/sources.list.d/passenger.list' sudo apt-get update fi green "Installing Passenger" sudo apt-get install -y libnginx-mod-http-passenger green "Activating Passenger" if [ ! -f /etc/nginx/modules-enabled/50-mod-http-passenger.conf ]; then sudo ln -s /usr/share/nginx/modules-available/mod-http-passenger.load /etc/nginx/modules-enabled/50-mod-http-passenger.conf fi green "Validating Installation" if ! passenger-config validate-install --auto | grep -Fq $(gem env gemdir); then red "Passenger seems is not using gem's folder!" passenger-config validate-install --auto exit 1 fi if grep -Fq "passenger_ruby $HOME/.rbenv/shims/ruby;" /etc/nginx/conf.d/mod-http-passenger.conf; then yellow "Passenger pointing correctly to rbenv environment in /etc/nginx/sites-enabled/decidim.conf" else green "recreating mod-http-passenger.conf to point to rbenv environment" sudo sed -i '/^passenger_ruby/c\passenger_ruby $HOME/.rbenv/shims/ruby;' /etc/nginx/conf.d/mod-http-passenger.conf fi if [ -x bin/delayed_job ]; then yellow "delayed_job binary already installed" else green "installing delayed_job binary and migrations" bin/rails generate delayed_job:active_record bin/rake db:migrate fi if [ -x bin/delayed_job_cron.sh ]; then yellow "Delayed job cron script already exists in $FOLDER/bin/" else green "Creating a job cron script in $FOLDER/bin" cat > bin/delayed_job_cron.sh <