21700: Install Bundler system-wide in Rails postinst
[arvados.git] / build / rails-package-scripts / postinst.sh
1 #!/bin/sh
2 # Copyright (C) The Arvados Authors. All rights reserved.
3 #
4 # SPDX-License-Identifier: AGPL-3.0
5
6 # This code runs after package variable definitions and step2.sh.
7
8 set -e
9
10 DATABASE_READY=1
11 APPLICATION_READY=1
12
13 report_not_ready() {
14     local ready_flag="$1"; shift
15     local config_file="$1"; shift
16     if [ "1" != "$ready_flag" ]; then cat >&2 <<EOF
17
18 PLEASE NOTE:
19
20 The $PACKAGE_NAME package was not configured completely because
21 $config_file needs some tweaking.
22 Please refer to the documentation at
23 <$DOC_URL> for more details.
24
25 When $(basename "$config_file") has been modified,
26 reconfigure or reinstall this package.
27
28 EOF
29     fi
30 }
31
32 report_web_service_warning() {
33     local warning="$1"; shift
34     cat >&2 <<EOF
35
36 WARNING: $warning.
37
38 To override, set the WEB_SERVICE environment variable to the name of the service
39 hosting the Rails server.
40
41 For Debian-based systems, then reconfigure this package with dpkg-reconfigure.
42
43 For RPM-based systems, then reinstall this package.
44
45 EOF
46 }
47
48 run_and_report() {
49     # Usage: run_and_report ACTION_MSG CMD
50     # This is the usual wrapper that prints ACTION_MSG, runs CMD, then writes
51     # a message about whether CMD succeeded or failed.  Returns the exit code
52     # of CMD.
53     local action_message="$1"; shift
54     local retcode=0
55     echo -n "$action_message..."
56     if "$@"; then
57         echo " done."
58     else
59         retcode=$?
60         echo " failed."
61     fi
62     return $retcode
63 }
64
65 setup_confdirs() {
66     for confdir in "$@"; do
67         if [ ! -d "$confdir" ]; then
68             install -d -g "$WWW_OWNER" -m 0750 "$confdir"
69         fi
70     done
71 }
72
73 setup_conffile() {
74     # Usage: setup_conffile CONFFILE_PATH [SOURCE_PATH]
75     # Both paths are relative to RELEASE_CONFIG_PATH.
76     # This function will try to safely ensure that a symbolic link for
77     # the configuration file points from RELEASE_CONFIG_PATH to CONFIG_PATH.
78     # If SOURCE_PATH is given, this function will try to install that file as
79     # the configuration file in CONFIG_PATH, and return 1 if the file in
80     # CONFIG_PATH is unmodified from the source.
81     local conffile_relpath="$1"; shift
82     local conffile_source="$1"
83     local release_conffile="$RELEASE_CONFIG_PATH/$conffile_relpath"
84     local etc_conffile="$CONFIG_PATH/$(basename "$conffile_relpath")"
85
86     # Note that -h can return true and -e will return false simultaneously
87     # when the target is a dangling symlink.  We're okay with that outcome,
88     # so check -h first.
89     if [ ! -h "$release_conffile" ]; then
90         if [ ! -e "$release_conffile" ]; then
91             ln -s "$etc_conffile" "$release_conffile"
92         # If there's a config file in /var/www identical to the one in /etc,
93         # overwrite it with a symlink after porting its permissions.
94         elif cmp --quiet "$release_conffile" "$etc_conffile"; then
95             local ownership="$(stat -c "%u:%g" "$release_conffile")"
96             local owning_group="${ownership#*:}"
97             if [ 0 != "$owning_group" ]; then
98                 chgrp "$owning_group" "$CONFIG_PATH" /etc/arvados
99             fi
100             chown "$ownership" "$etc_conffile"
101             chmod --reference="$release_conffile" "$etc_conffile"
102             ln --force -s "$etc_conffile" "$release_conffile"
103         fi
104     fi
105
106     if [ -n "$conffile_source" ]; then
107         if [ ! -e "$etc_conffile" ]; then
108             install -g "$WWW_OWNER" -m 0640 \
109                     "$RELEASE_CONFIG_PATH/$conffile_source" "$etc_conffile"
110             return 1
111         # Even if $etc_conffile already existed, it might be unmodified from
112         # the source.  This is especially likely when a user installs, updates
113         # database.yml, then reconfigures before they update application.yml.
114         # Use cmp to be sure whether $etc_conffile is modified.
115         elif cmp --quiet "$RELEASE_CONFIG_PATH/$conffile_source" "$etc_conffile"; then
116             return 1
117         fi
118     fi
119 }
120
121 prepare_database() {
122   DB_MIGRATE_STATUS=`bin/rake db:migrate:status 2>&1 || true`
123   if echo "$DB_MIGRATE_STATUS" | grep -qF 'Schema migrations table does not exist yet.'; then
124       # The database exists, but the migrations table doesn't.
125       run_and_report "Setting up database" bin/rake \
126                      "$RAILSPKG_DATABASE_LOAD_TASK" db:seed
127   elif echo "$DB_MIGRATE_STATUS" | grep -q '^database: '; then
128       run_and_report "Running db:migrate" \
129                      bin/rake db:migrate
130   elif echo "$DB_MIGRATE_STATUS" | grep -q 'database .* does not exist'; then
131       if ! run_and_report "Running db:setup" \
132            bin/rake db:setup 2>/dev/null; then
133           echo "Warning: unable to set up database." >&2
134           DATABASE_READY=0
135       fi
136   else
137     echo "Warning: Database is not ready to set up. Skipping database setup." >&2
138     DATABASE_READY=0
139   fi
140 }
141
142 configure_version() {
143   if [ -n "$WEB_SERVICE" ]; then
144       SERVICE_MANAGER=$(guess_service_manager)
145   elif WEB_SERVICE=$(list_services_systemd | grep -E '^(nginx|httpd)'); then
146       SERVICE_MANAGER=systemd
147   elif WEB_SERVICE=$(list_services_service \
148                          | grep -Eo '\b(nginx|httpd)[^[:space:]]*'); then
149       SERVICE_MANAGER=service
150   fi
151
152   if [ -z "$WEB_SERVICE" ]; then
153     report_web_service_warning "Web service (Nginx or Apache) not found"
154   elif [ "$WEB_SERVICE" != "$(echo "$WEB_SERVICE" | head -n 1)" ]; then
155     WEB_SERVICE=$(echo "$WEB_SERVICE" | head -n 1)
156     report_web_service_warning \
157         "Multiple web services found.  Choosing the first one ($WEB_SERVICE)"
158   fi
159
160   if [ -e /etc/redhat-release ]; then
161       # Recognize any service that starts with "nginx"; e.g., nginx16.
162       if [ "$WEB_SERVICE" != "${WEB_SERVICE#nginx}" ]; then
163         WWW_OWNER=nginx
164       else
165         WWW_OWNER=apache
166       fi
167   else
168       # Assume we're on a Debian-based system for now.
169       # Both Apache and Nginx run as www-data by default.
170       WWW_OWNER=www-data
171   fi
172
173   echo
174   echo "Assumption: $WEB_SERVICE is configured to serve Rails from"
175   echo "            $RELEASE_PATH"
176   echo "Assumption: $WEB_SERVICE and passenger run as $WWW_OWNER"
177   echo
178
179   echo -n "Creating symlinks to configuration in $CONFIG_PATH ..."
180   setup_confdirs /etc/arvados "$CONFIG_PATH"
181   setup_conffile environments/production.rb environments/production.rb.example \
182       || true
183   setup_extra_conffiles
184   echo "... done."
185
186   # Before we do anything else, make sure some directories and files are in place
187   if [ ! -e $SHARED_PATH/log ]; then mkdir -p $SHARED_PATH/log; fi
188   if [ ! -e $RELEASE_PATH/tmp ]; then mkdir -p $RELEASE_PATH/tmp; fi
189   if [ ! -e $RELEASE_PATH/log ]; then ln -s $SHARED_PATH/log $RELEASE_PATH/log; fi
190   if [ ! -e $SHARED_PATH/log/production.log ]; then touch $SHARED_PATH/log/production.log; fi
191
192   cd "$RELEASE_PATH"
193   export RAILS_ENV=production
194
195   run_and_report "Installing bundler" gem install --conservative --version '~> 2.4.0' bundler
196   local bundle="$(gem contents --version '~> 2.4.0' bundler | grep '/exe/bundle$' | tail -n1)"
197   if ! [ -x "$bundle" ]; then
198       echo "Error: failed to find \`bundle\` command after installing bundler gem" >&2
199       return 1
200   fi
201
202   local bundle_path="$SHARED_PATH/vendor_bundle"
203   run_and_report "Running bundle config set --local path $SHARED_PATH/vendor_bundle" \
204                  "$bundle" config set --local path "$bundle_path"
205
206   # As of April 2024/Bundler 2.4, `bundle install` tends not to install gems
207   # which are already installed system-wide, which causes bundle activation to
208   # fail later. Work around this by installing all gems manually.
209   find vendor/cache -maxdepth 1 -name '*.gem' -print0 \
210       | run_and_report "Installing bundle gems" xargs -0r \
211                        gem install --conservative --ignore-dependencies --local --quiet \
212                        --install-dir="$bundle_path/ruby/$(ruby -e 'puts RUBY_VERSION')"
213   run_and_report "Running bundle install" "$bundle" install --prefer-local --quiet
214   run_and_report "Verifying bundle is complete" "$bundle" exec true
215
216   echo -n "Ensuring directory and file permissions ..."
217   # Ensure correct ownership of a few files
218   chown "$WWW_OWNER:" $RELEASE_PATH/config/environment.rb
219   chown "$WWW_OWNER:" $RELEASE_PATH/config.ru
220   chown "$WWW_OWNER:" $RELEASE_PATH/Gemfile.lock
221   chown -R "$WWW_OWNER:" $RELEASE_PATH/tmp || true
222   chown -R "$WWW_OWNER:" $SHARED_PATH/log
223   # Make sure postgres doesn't try to use a pager.
224   export PAGER=
225   case "$RAILSPKG_DATABASE_LOAD_TASK" in
226       # db:structure:load was deprecated in Rails 6.1 and shouldn't be used.
227       db:schema:load | db:structure:load)
228           chown "$WWW_OWNER:" $RELEASE_PATH/db/schema.rb || true
229           chown "$WWW_OWNER:" $RELEASE_PATH/db/structure.sql || true
230           ;;
231   esac
232   chmod 644 $SHARED_PATH/log/*
233   chmod -R 2775 $RELEASE_PATH/tmp || true
234   echo "... done."
235
236   if [ -n "$RAILSPKG_DATABASE_LOAD_TASK" ]; then
237       prepare_database
238   fi
239
240   if [ -e /etc/arvados/config.yml ]; then
241       # warn about config errors (deprecated/removed keys from
242       # previous version, etc)
243       run_and_report "Checking configuration for completeness" \
244                      bin/rake config:check || APPLICATION_READY=0
245   else
246       APPLICATION_READY=0
247   fi
248
249   chown -R "$WWW_OWNER:" $RELEASE_PATH/tmp
250
251   setup_before_nginx_restart
252
253   if [ -n "$SERVICE_MANAGER" ]; then
254       service_command "$SERVICE_MANAGER" restart "$WEB_SERVICE"
255   fi
256 }
257
258 if [ "$1" = configure ]; then
259   # This is a debian-based system
260   configure_version
261 elif [ "$1" = "0" ] || [ "$1" = "1" ] || [ "$1" = "2" ]; then
262   # This is an rpm-based system
263   configure_version
264 fi
265
266 report_not_ready "$APPLICATION_READY" "/etc/arvados/config.yml"