]> git.arvados.org - arvados.git/blob - build/rails-package-scripts/postinst.sh
22581: Fix typo
[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.
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 # This will be set to a command path after we install the version we need.
25 BUNDLE=
26
27 # systemd_ctl is just "systemctl if we booted with systemd, otherwise a noop."
28 # This makes the package installable in Docker containers, albeit without any
29 # service deployment.
30 if [ -d /run/systemd/system ]; then
31     systemd_ctl() { systemctl "$@"; }
32 else
33     systemd_ctl() { true; }
34 fi
35
36 systemd_quote() {
37     if [ $# -ne 1 ]; then
38         echo "error: systemd_quote requires exactly one argument" >&2
39         return 2
40     fi
41     # See systemd.syntax(7) - Use double quotes with backslash escapes
42     echo "$1" | sed -re 's/[\\"]/\\\0/g; s/^/"/; s/$/"/'
43 }
44
45 run_and_report() {
46     # Usage: run_and_report ACTION_MSG CMD
47     # This is the usual wrapper that prints ACTION_MSG, runs CMD, then writes
48     # a message about whether CMD succeeded or failed.  Returns the exit code
49     # of CMD.
50     local action_message="$1"; shift
51     local retcode=0
52     echo -n "$action_message..."
53     if "$@"; then
54         echo " done."
55     else
56         retcode=$?
57         echo " failed."
58     fi
59     return $retcode
60 }
61
62 report_not_ready() {
63     local exitcode="$1"; shift
64     local reason="$1"; shift
65     local doc_url="${1:-}"; shift
66     case "$doc_url" in
67         http://* | https://* ) ;;
68         /*) doc_url="https://doc.arvados.org${doc_url}" ;;
69         \#*) doc_url="https://doc.arvados.org/install/install-api-server.html${doc_url}" ;;
70         *) doc_url="https://doc.arvados.org/install/${doc_url}" ;;
71     esac
72     cat >&2 <<EOF
73 NOTE: The $PACKAGE_NAME package was not configured completely because
74 $reason.
75 Please refer to the documentation for next steps:
76   <$doc_url>
77
78 After you do that, resume $PACKAGE_NAME setup by running:
79   $RESETUP_CMD
80 EOF
81     exit "${exitcode:-20}"
82 }
83
84 setup_confdirs() {
85     local confdir confgrp
86     case "$WWW_OWNER" in
87         "") confgrp=root ;;
88         *) confgrp="$WWW_OWNER" ;;
89     esac
90     for confdir in "$@"; do
91         if [ ! -d "$confdir" ]; then
92             install -d -g "$confgrp" -m 0750 "$confdir"
93         fi
94     done
95 }
96
97 setup_conffile() {
98     # Usage: setup_conffile CONFFILE_PATH [SOURCE_PATH]
99     # Both paths are relative to RELEASE_CONFIG_PATH.
100     # This function will try to safely ensure that a symbolic link for
101     # the configuration file points from RELEASE_CONFIG_PATH to CONFIG_PATH.
102     # If SOURCE_PATH is given, this function will try to install that file as
103     # the configuration file in CONFIG_PATH, and return 1 if the file in
104     # CONFIG_PATH is unmodified from the source.
105     local conffile_relpath="$1"; shift
106     local conffile_source="$1"
107     local release_conffile="$RELEASE_CONFIG_PATH/$conffile_relpath"
108     local etc_conffile="$CONFIG_PATH/$(basename "$conffile_relpath")"
109
110     # Note that -h can return true and -e will return false simultaneously
111     # when the target is a dangling symlink.  We're okay with that outcome,
112     # so check -h first.
113     if [ ! -h "$release_conffile" ]; then
114         if [ ! -e "$release_conffile" ]; then
115             ln -s "$etc_conffile" "$release_conffile"
116         # If there's a config file in /var/www identical to the one in /etc,
117         # overwrite it with a symlink after porting its permissions.
118         elif cmp --quiet "$release_conffile" "$etc_conffile"; then
119             local ownership="$(stat -c "%u:%g" "$release_conffile")"
120             local owning_group="${ownership#*:}"
121             if [ 0 != "$owning_group" ]; then
122                 chgrp "$owning_group" "$CONFIG_PATH" /etc/arvados
123             fi
124             chown "$ownership" "$etc_conffile"
125             chmod --reference="$release_conffile" "$etc_conffile"
126             ln --force -s "$etc_conffile" "$release_conffile"
127         fi
128     fi
129
130     if [ -n "$conffile_source" ]; then
131         if [ ! -e "$etc_conffile" ]; then
132             install -g "$WWW_OWNER" -m 0640 \
133                     "$RELEASE_CONFIG_PATH/$conffile_source" "$etc_conffile"
134             return 1
135         # Even if $etc_conffile already existed, it might be unmodified from
136         # the source.  This is especially likely when a user installs, updates
137         # database.yml, then reconfigures before they update application.yml.
138         # Use cmp to be sure whether $etc_conffile is modified.
139         elif cmp --quiet "$RELEASE_CONFIG_PATH/$conffile_source" "$etc_conffile"; then
140             return 1
141         fi
142     fi
143 }
144
145 prepare_database() {
146   # Prevent PostgreSQL from trying to page output
147   unset PAGER
148   DB_MIGRATE_STATUS=`"$BUNDLE" exec bin/rake db:migrate:status 2>&1 || true`
149   if echo "$DB_MIGRATE_STATUS" | grep -qF 'Schema migrations table does not exist yet.'; then
150       # The database exists, but the migrations table doesn't.
151       run_and_report "Setting up database" "$BUNDLE" exec bin/rake db:schema:load db:seed
152   elif echo "$DB_MIGRATE_STATUS" | grep -q '^database: '; then
153       run_and_report "Running db:migrate" "$BUNDLE" exec bin/rake db:migrate
154   elif echo "$DB_MIGRATE_STATUS" | grep -q 'database .* does not exist'; then
155       run_and_report "Running db:setup" "$BUNDLE" exec bin/rake db:setup
156   else
157       # We don't have enough configuration to even check the database.
158       return 1
159   fi
160 }
161
162 case "$DISTRO_FAMILY" in
163     debian) WWW_OWNER=www-data ;;
164     rhel) WWW_OWNER="$(id --group --name nginx || true)" ;;
165 esac
166
167 # Before we do anything else, make sure some directories and files are in place
168 if [ ! -e $SHARED_PATH/log ]; then mkdir -p $SHARED_PATH/log; fi
169 if [ ! -e $RELEASE_PATH/tmp ]; then mkdir -p $RELEASE_PATH/tmp; fi
170 if [ ! -e $RELEASE_PATH/log ]; then ln -s $SHARED_PATH/log $RELEASE_PATH/log; fi
171 if [ ! -e $SHARED_PATH/log/production.log ]; then touch $SHARED_PATH/log/production.log; fi
172
173 cd "$RELEASE_PATH"
174 export RAILS_ENV=production
175
176 run_and_report "Installing bundler" gem install --conservative --version '~> 2.4.0' bundler
177 ruby_minor_ver="$(ruby -e 'puts RUBY_VERSION.split(".")[..1].join(".")')"
178 BUNDLE="$(gem contents --version '~> 2.4.0' bundler | grep -E '/(bin|exe)/bundle$' | tail -n1)"
179 if ! [ -x "$BUNDLE" ]; then
180     # Some distros (at least Ubuntu 24.04) append the Ruby version to the
181     # executable name, but that isn't reflected in the output of
182     # `gem contents`. Check for that version.
183     BUNDLE="$BUNDLE$ruby_minor_ver"
184     if ! [ -x "$BUNDLE" ]; then
185         echo "Error: failed to find \`bundle\` command after installing bundler gem" >&2
186         exit 11
187     fi
188 fi
189
190 bundle_path="$SHARED_PATH/vendor_bundle"
191 run_and_report "Running bundle config set --local path $SHARED_PATH/vendor_bundle" \
192                "$BUNDLE" config set --local path "$bundle_path"
193
194 # As of April 2024/Bundler 2.4, `bundle install` tends not to install gems
195 # which are already installed system-wide, which causes bundle activation to
196 # fail later. Prevent this by trying to pre-install all gems manually.
197 # `gem install` can fail if there are conflicts between gems installed by
198 # previous versions and gems installed by the current version. Ignore those
199 # errors; all that matters is that we get `bundle install` to succeed, and
200 # we check that next. <https://dev.arvados.org/issues/22647>
201 echo "Preinstalling bundle gems -- conflict errors are OK..."
202 find vendor/cache -maxdepth 1 -name '*.gem' -print0 |
203     xargs -0r gem install --conservative --ignore-dependencies \
204           --local --no-document --quiet \
205           --install-dir="$bundle_path/ruby/$ruby_minor_ver.0" ||
206     true
207 echo " done."
208 run_and_report "Running bundle install" "$BUNDLE" install --prefer-local --quiet
209 run_and_report "Verifying bundle is complete" "$BUNDLE" exec true
210
211 passenger="$("$BUNDLE" exec gem contents passenger | grep -E '/(bin|exe)/passenger$' | tail -n1)"
212 if ! [ -x "$passenger" ]; then
213     echo "Error: failed to find \`passenger\` command after installing bundle" >&2
214     exit 12
215 fi
216 "$BUNDLE" exec "$passenger-config" build-native-support
217 # `passenger-config install-standalone-runtime` downloads an agent, but at
218 # least with Passenger 6.0.23 (late 2024), that version tends to segfault.
219 # Compiling our own is safer.
220 "$BUNDLE" exec "$passenger-config" compile-agent --auto --optimize
221 "$BUNDLE" exec "$passenger-config" install-standalone-runtime --auto --brief
222
223 echo -n "Creating symlinks to configuration in $CONFIG_PATH ..."
224 setup_confdirs /etc/arvados "$CONFIG_PATH"
225 setup_conffile environments/production.rb environments/production.rb.example \
226     || true
227 # Rails 5.2 does not tolerate dangling symlinks in the initializers
228 # directory, and this one can still be there, left over from a previous
229 # version of the API server package.
230 rm -f $RELEASE_PATH/config/initializers/omniauth.rb
231 echo "... done."
232
233 echo -n "Extending systemd unit configuration ..."
234 if [ -z "$WWW_OWNER" ]; then
235     systemd_group="%N"
236 else
237     systemd_group="$(systemd_quote "$WWW_OWNER")"
238 fi
239 install -d /lib/systemd/system/arvados-railsapi.service.d
240 # The 20 prefix is chosen so most user overrides should come after, which
241 # is what most admins will expect, but there's still space to put drop-ins
242 # earlier.
243 cat >/lib/systemd/system/arvados-railsapi.service.d/20-postinst.conf <<EOF
244 [Service]
245 ExecStartPre=+/bin/chgrp $systemd_group log tmp
246 ExecStartPre=+-/bin/chgrp $systemd_group \${PASSENGER_LOG_FILE}
247 ExecStart=
248 ExecStart=$(systemd_quote "$BUNDLE") exec $(systemd_quote "$passenger") start --daemonize --pid-file %t/%N/passenger.pid
249 ExecStop=
250 ExecStop=$(systemd_quote "$BUNDLE") exec $(systemd_quote "$passenger") stop --pid-file %t/%N/passenger.pid
251 ExecReload=
252 ExecReload=$(systemd_quote "$BUNDLE") exec $(systemd_quote "$passenger-config") reopen-logs
253 ${WWW_OWNER:+SupplementaryGroups=$WWW_OWNER}
254 EOF
255 systemd_ctl daemon-reload
256 echo "... done."
257
258 # warn about config errors (deprecated/removed keys from
259 # previous version, etc)
260 if ! run_and_report "Checking configuration for completeness" "$BUNDLE" exec bin/rake config:check; then
261     report_not_ready 21 "you must add required configuration settings to /etc/arvados/config.yml" "#update-config"
262 elif ! prepare_database; then
263     report_not_ready 22 "database setup could not be completed"
264 else
265     systemd_ctl try-restart arvados-railsapi.service
266 fi