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