homing in on proper powerup behavior
[feisty_meow.git] / scripts / site_avenger / shared_site_mgr.sh
1 #!/bin/bash
2
3 # Author: Kevin Wentworth
4 # Author: Chris Koeritz
5
6 # This contains a bunch of reusable functions that help out in managing websites.
7
8 # This script is sourced, and relies on the value of THISDIR, which should
9 # point at the directory where the site management scripts are stored,
10 # especially this one.
11
12 source "$FEISTY_MEOW_SCRIPTS/core/launch_feisty_meow.sh"
13
14 export SSM_LOG_FILE="$TMP/$(logname)-siteavenger-script.log"
15
16 # configure feisty revision control to ignore vendor folders.
17 export NO_CHECKIN_VENDOR=true
18
19 # handles the computation of the base application path and the app dir name.
20 # this expects to be passed the application directory name, but it will attempt to
21 # do something intelligent if no name is passed in.
22 function autoconfigure_paths()
23 {
24   export app_dirname="$1"; shift
25
26   if [ -z "$app_dirname" ]; then
27     echo "$(date_stringer): Guessing application dir from local folder."
28     app_dirname="$(basename $(\pwd))"
29     export BASE_APPLICATION_PATH="$(dirname $(\pwd))"
30 echo "calculated application dir of '$app_dirname' and"
31 echo "a base app path of '$BASE_APPLICATION_PATH'"
32   fi
33
34   # get our configuration loaded, if we know the config file.
35   # if there is none, we will use our default version.
36   export SITE_MANAGEMENT_CONFIG_FILE
37   if [ -z "$SITE_MANAGEMENT_CONFIG_FILE" ]; then
38     SITE_MANAGEMENT_CONFIG_FILE="$THISDIR/config/default.app"
39     echo "$(date_stringer): Site management config file was not set.  Using default:" >> "$SSM_LOG_FILE"
40     echo "$(date_stringer):   $SITE_MANAGEMENT_CONFIG_FILE" >> "$SSM_LOG_FILE"
41   fi
42
43   # load in at least the default version to get us moving.
44   source "$SITE_MANAGEMENT_CONFIG_FILE"
45   exit_on_error "loading site management configuration from: $SITE_MANAGEMENT_CONFIG_FILE"
46
47
48 echo "after site config file sourced, app dirname now '$app_dirname' and"
49 echo "base app path now '$BASE_APPLICATION_PATH'"
50
51 }
52
53 # tests that the main storage folder for apps exists.
54 # the parameter passed in should be the application directory name (app_dirname), without
55 # any additional path components.  the script will attempt to auto-configure the application
56 # base path (above the project folder with app_dirname) and get all the other path variables
57 # established.
58 function check_apps_root()
59 {
60   local temp_app_dirname="$1"; shift
61
62 echo new call to auto conf func...
63   autoconfigure_paths "$temp_app_dirname"
64 echo after call to auto conf func...
65
66   if [ -z "$BASE_APPLICATION_PATH" ]; then
67 echo fix this: we had no base app path, what to do now?
68 exit 1
69   fi
70
71   if [ ! -d "$BASE_APPLICATION_PATH" ]; then
72     echo "$(date_stringer): Creating the apps directory: $BASE_APPLICATION_PATH" >> "$SSM_LOG_FILE"
73     mkdir "$BASE_APPLICATION_PATH"
74     exit_on_error "Making apps directory when not already present"
75   fi
76 }
77
78 #hmmm: extract to core somewhere...
79 # locates a parent directory of a certain name, if possible.  returns success
80 # (as zero) if the directory was found, and failure if there was no parent
81 # named as requested.  sets a global variable PARENT_DIR_FOUND to the full
82 # directory name that matched, including the name being sought (but omitting
83 # any deeper directories than that).
84 function find_named_parent_dir()
85 {
86   local dir_name_sought="$1"; shift
87   # clear any previous global result.
88   unset PARENT_DIR_FOUND
89   # check for degenerate case of parameter count.
90   if [ -z "$dir_name_sought" ]; then
91     echo "
92 find_named_parent_dir: requires a directory name parameter, which will be
93 sought out above the current directory.  the return value indicates whether
94 the requested name was found or not.
95 "
96     return 1
97   fi
98   # signal a failure by default with our return value.
99   local retval=1
100   # loop upwards in dir hierarchy to find the name.
101   while true; do
102     local currdir="$(\pwd)"
103     if [ "$currdir" == "/" ]; then
104       # we climbed out of all subdirs.  this is a failure case.
105       retval=1
106       break
107     fi
108     # get the base part of our name to check on success.
109     local base="$(basename "$currdir")"
110     if [ "$base" == "$dir_name_sought" ]; then
111       # yes, that is the right name.  success case.  save our result.
112       export PARENT_DIR_FOUND="$currdir"
113       retval=0
114       break
115     fi
116     # hop up a directory.
117     pushd .. &>/dev/null
118   done
119
120   # rollback any directories we pushed.
121   while popd &>/dev/null; do true; done
122
123   return $retval
124 }
125
126 # tries to find an appropriate config file for the application.
127 function locate_config_file()
128 {
129   local app_dirname="$1"; shift
130
131   local configfile="$THISDIR/config/${app_dirname}.app"
132   echo "$(date_stringer): config file guessed?: $configfile" >> "$SSM_LOG_FILE"
133   if [ ! -f "$configfile" ]; then
134     # this is not a good config file.  we can't auto-guess the config.
135     echo -e "$(date_stringer): 
136 There is no specific site configuration file in:
137   $configfile
138 We will continue onward using the default and hope that this project follows
139 the standard pattern for cakephp projects." >> "$SSM_LOG_FILE"
140     # we'll pull in the default config file we set earlier; this will
141     # reinitialize some variables based on the app name.
142   else
143     # they gave us a valid config file.  let's try using it.
144     export SITE_MANAGEMENT_CONFIG_FILE="$configfile"
145   fi
146
147   # try to load the config.
148   source "$SITE_MANAGEMENT_CONFIG_FILE"
149   exit_on_error "loading site management configuration from: $SITE_MANAGEMENT_CONFIG_FILE"
150
151   return 0
152 }
153
154 # this function will seek out top-level directories in the target directory passed in.
155 # if there is only one directory, then it is returned (in the app_dirname variable).
156 # otherwise, the user is asked which directory to use.
157 # important: this sets a global variable app_dirname to the application's directory name.
158 function find_app_folder()
159 {
160   local appsdir="$1"; shift
161
162   # throw away any prior value so no confusion arises.
163   unset app_dirname
164   
165   # count number of directories...  if exactly one, then choose it.
166   numdirs=$(count_directories "$appsdir/")
167
168   if [ $numdirs -eq 0 ]; then
169     sep
170     echo "There are no directories in the application directory:"
171     echo "  $appsdir"
172     echo "Please create a directory for the site storage, based on the application"
173     echo "name that you want to work on.  Or you can just pass the directory name"
174     echo "on the command line, e.g.:"
175     echo "  $(basename $0) turtle"
176     sep
177     return 1
178   elif [ $numdirs -eq 1 ]; then
179     # one directory in apps, so we'll pick that one.
180     app_dirname="$(basename $(find "$appsdir" -follow -mindepth 1 -maxdepth 1 -type d) )"
181     exit_on_error "Guessing application folder"
182   else
183     # there's more than one folder in apps...
184
185     # make sure we're allowed to auto-guess the folder name from our current dir.
186     if [ -z "$NO_AUTOMATIC_FOLDER_GUESS" ]; then
187       # if we can find the special checkout directory name above our current PWD, then that
188       # might tell us our project name.
189       if  find_named_parent_dir "$CHECKOUT_DIR_NAME"; then
190         # we can grab a name above the checkout dir name location.  let's try that.
191         app_dirname="$(basename "$(dirname $PARENT_DIR_FOUND)" )"
192       fi
193     else
194       # flag maintenance, to avoid hosing other commands by leaving this set.
195       unset NO_AUTOMATIC_FOLDER_GUESS
196
197       # well, we couldn't guess a directory based on our current location,
198       # so ask the user to choose.
199       # Reference: https://askubuntu.com/questions/1705/how-can-i-create-a-select-menu-in-a-shell-script
200       holdps3="$PS3"
201       PS3='Please pick a folder for site initialization: '
202       options=( $(find "$appsdir" -follow -mindepth 1 -maxdepth 1 -type d -exec basename {} ';') "Quit")
203       select app_dirname in "${options[@]}"; do
204         case $app_dirname in
205           "Quit") echo ; echo "Quitting from the script."; return 1; ;;
206           *) echo ; echo "You picked folder '$app_dirname'" ; break; ;;
207         esac
208       done
209       if [ -z "$app_dirname" ]; then
210         echo "The folder was not provided.  This script needs a directory name"
211         echo "within which to initialize the site."
212         return 1
213       fi
214       PS3="$holdps3"
215     fi
216   fi
217   test_app_folder "$appsdir" "$app_dirname"
218   exit_on_error "Testing application folder: $app_dirname"
219
220   echo "Application folder is: $app_dirname"
221   return 0
222 }
223
224 # ensures that the app directory name is valid and then loads the config
225 # for the app (either via a specific file or using the defaults).
226 function test_app_folder()
227 {
228   local appsdir="$1"; shift
229   local dir="$1"; shift
230
231   local combo="$appsdir/$dir"
232
233   if [ ! -d "$combo" ]; then
234     echo "$(date_stringer): Creating app directory: $combo" >> "$SSM_LOG_FILE"
235     mkdir "$combo"
236     exit_on_error "Making application directory when not already present"
237   fi
238
239 echo yo combo is $combo
240
241   if [ -d "$combo/$CHECKOUT_DIRNAME" ]; then
242     echo "Dropping expectation for intermediary checkout directory name."
243     unset CHECKOUT_DIRNAME
244   fi
245
246   locate_config_file "$dir"
247 }
248
249 # eases some permissions to enable apache to write log files and do other shopkeeping.
250 function fix_site_perms()
251 {
252   local app_dir="$1"; shift
253
254   local site_dir="$app_dir/$CHECKOUT_DIR_NAME"
255
256   if [ -f "$site_dir/bin/cake" ]; then
257     sudo chmod -R a+rx "$site_dir/bin/cake"
258     exit_on_error "Enabling execute bit on cake binary"
259   fi
260
261   if [ -d "$site_dir/logs" ]; then
262     sudo chmod -R g+w "$site_dir/logs"
263     exit_on_error "Enabling group write on site's Logs directory"
264   fi
265
266   if [ -d "$site_dir/tmp" ]; then
267     sudo chmod -R g+w "$site_dir/tmp"
268     exit_on_error "Enabling group write on site's tmp directory"
269   fi
270 }
271
272 # tosses out any cached object data that originated from the database.
273 function clear_orm_cache()
274 {
275   local site_dir="$1"; shift
276
277   if [ -f "$site_dir/bin/cake" ]; then
278     # flush any cached objects from db.
279     "$site_dir/bin/cake" orm_cache clear
280     exit_on_error "Clearing ORM cache"
281   fi
282 }
283
284 # updates the revision control repository passed in.  this expects that the
285 # repo will live in a folder called "checkout_dirname" under the app path,
286 # which is the standard for deployed site avenger sites.  if that directory is
287 # missing, then we assume a checkout of the top-level repository instead.
288 # important: this also sets a global variable called site_store_path to the full
289 # path of the application.
290 function update_repo()
291 {
292   local full_app_dir="$1"; shift
293   local checkout_dirname="$1"; shift
294   local repo_root="$1"; shift
295   local repo_name="$1"; shift
296
297 echo "$(date_stringer): here are parms in update repo:" >> "$SSM_LOG_FILE"
298 echo "$(date_stringer): $(var full_app_dir checkout_dirname repo_root repo_name)" >> "$SSM_LOG_FILE"
299
300   # forget any prior value, since we are going to validate the path.
301   unset site_store_path
302
303   pushd "$full_app_dir" &>/dev/null
304   exit_on_error "Switching to our app dir '$full_app_dir'"
305
306   local complete_path="$full_app_dir"
307   if [ ! -z "$checkout_dirname" ]; then
308     # make the full path using the non-empty checkout dir name.
309     complete_path+="/$checkout_dirname"
310   fi
311
312   # see if the checkout directory exits.  the repo_found variable is set to
313   # non-empty if we find it and it's a valid git repo.
314   repo_found=
315   if [ -d "$full_app_dir" ]; then
316     # checkout directory exists, so let's check it.
317     pushd "$full_app_dir" &>/dev/null
318     exit_on_error "Switching to directory for check out: $full_app_dir"
319
320     # ask for repository name (without .git).
321     if git rev-parse --git-dir > /dev/null 2>&1; then
322       # this is a valid git repo.
323       repo_found=yes
324     fi
325  
326     # we don't consider the state of having the dir exist but the repo be wrong as good.
327     if [ -z "$repo_found" ]; then
328       echo "There is a problem; this folder is not a valid repository:"
329       echo "  $full_app_dir"
330       echo "This script cannot continue unless the git repository is valid."
331       exit 1
332     fi
333     popd &>/dev/null
334   fi
335
336   if [ ! -z "$repo_found" ]; then
337     # a repository was found, so update the version here and leave.
338     echo "Repository $repo_name exists.  Updating it."
339     rgetem
340     exit_on_error "Recursive checkout on: $complete_path"
341   else
342     # clone the repo since it wasn't found.
343     echo "Cloning repository $repo_name now."
344     git clone "$repo_root/$repo_name.git" $checkout_dirname
345     exit_on_error "Git clone of repository: $repo_name"
346   fi
347
348 #unused?
349   # construct the full path to where the app will actually live.
350   site_store_path="$complete_path"
351
352   popd &>/dev/null
353 }
354
355 # this function goes to the directory specified and makes it right with
356 # composer install.  this is as opposed to composer update, which could
357 # change the state. 
358 function composer_repuff()
359 {
360   local site_store_path="$1"; shift
361
362   pushd "$site_store_path" &>/dev/null
363   exit_on_error "Switching to our app dir '$site_store_path'"
364
365   echo "Updating site with composer..."
366
367   composer -n install
368   exit_on_error "Composer installation step on '$site_store_path'."
369   echo "Site updated."
370
371 #hmmm: argh global
372   dir="$site_store_path/$CHECKOUT_DIR_NAME/vendor/siteavenger/avcore"
373   if [ -d "$dir" ]; then
374     echo "Running avcore database migrations..."
375     logfile="$TMP/problem-avcore_db_migration-$(date_stringer).log"
376     ./bin/cake migrations migrate -p Avcore &>"$logfile"
377     if [ $? -ne 0 ]; then
378       echo "** FAILED: Database migrations for avcore.  Check log file in: $logfile"
379       # we keep going, because some sites aren't ready for this yet.
380     else
381       \rm "$logfile"
382       echo "Database for avcore migrated."
383     fi
384   fi
385
386   clear_orm_cache
387
388   popd &>/dev/null
389 }
390
391 # this function creates the links needed to make the site function properly given our
392 # various dependencies and infrastructure.
393 function create_site_links()
394 {
395   local site_store_path="$1"; shift
396   local theme_name="$1"; shift
397
398   echo "Creating symbolic links for site assets..."
399
400   # jump into the site path so we can start making relative links.
401   pushd "$site_store_path" &>/dev/null
402   exit_on_error "Switching to our app dir '$site_store_path'"
403
404   pushd webroot &>/dev/null
405
406   # remove all symlinks that might plague us.
407   find . -maxdepth 1 -type l -exec rm -f {} ';'
408   exit_on_error "Cleaning out links in webroot"
409
410   # link in the avcore plugin.
411   make_safe_link "../vendor/siteavenger/avcore/webroot" avcore
412
413   # make the link for our theme as a lower-case version of the theme.
414   themelower=${theme_name,,}
415   make_safe_link "../plugins/$theme_name/webroot" "$themelower"
416
417   # link in any favicon files.
418   if [ -d "../plugins/$theme_name/favicon" ]; then
419     local fave
420     for fave in "../plugins/$theme_name/favicon"/*; do
421       make_safe_link "$fave" .
422     done
423   fi
424
425   # get back out of webroot.
426   popd &>/dev/null
427
428   # hop up a level above where we had been.
429   pushd .. &>/dev/null
430
431   # link 'public' to webroot.
432   if [ -L public ]; then
433     # public is a symlink.
434     \rm public
435     exit_on_error "Removing public directory symlink"
436   elif [ -d public ]; then
437     # public is a folder with default files.
438 #hmmm: is that safe?
439     \rm -rf public
440     exit_on_error "Removing public directory and contents"
441   fi
442
443   # create the main 'public' symlink
444 #hmmm: argh global
445   make_safe_link $CHECKOUT_DIR_NAME/webroot public
446   exit_on_error "Creating link to webroot called 'public'"
447
448 #hmmm: public/$themelower/im will be created automatically by system user with appropriate permissions
449
450   echo Created symbolic links.
451
452   popd &>/dev/null
453   popd &>/dev/null
454 }
455
456 # fetches composer to make sure it's up to date.
457 # (if powerup runs, composer install doesn't update git origin.)
458 function update_composer_repository()
459 {
460   local site_store_path="$1"; shift
461
462   pushd "$site_store_path" &>/dev/null
463
464   if git config remote.composer.url &>/dev/null; then
465     git pull composer
466     echo "Updated the composer repository."
467   else
468     echo "No composer repository was found for updating."
469   fi
470 }
471
472 # fixes the ownership for a site avenger or php application.
473 # this almost certainly will require sudo capability, if there are any ownership problems
474 # that need to be resolved.
475 function fix_appdir_ownership()
476 {
477   local appsdir="$1"; shift
478   local dir="$1"; shift
479
480   local combo="$appsdir/$dir"
481
482   # go with the default user running the script.
483   user_name="$USER"
484   if [ ! -z "$user_name" -a "$user_name" != "root" ]; then
485     echo "$(date_stringer): Chowning the app folder to be owned by: $user_name" >> "$SSM_LOG_FILE"
486 #hmmm: have to hope for now for standard group named after user 
487     sudo chown -R "$user_name:$user_name" "$combo"
488     exit_on_error "Chowning $combo to be owned by $user_name"
489   else
490     echo "$(date_stringer): user name failed checks for chowning, was found as '$user_name'" >> "$SSM_LOG_FILE"
491   fi
492
493   # 
494 #probably not enough for path!
495   fix_site_perms "$combo"
496 }
497
498 # Jumps to an application directory given the app name.  If no app name is
499 # given, it will show a menu to pick the app.
500 function switch_to()
501 {
502   # check for parameters.
503   app_dirname="$1"; shift
504
505   check_apps_root "$app_dirname"
506
507   # find proper webroot where the site will be initialized.
508   if [ -z "$app_dirname" ]; then
509     # no dir was passed, so guess it.
510     export NO_AUTOMATIC_FOLDER_GUESS=true
511     find_app_folder "$BASE_APPLICATION_PATH"
512   else
513     test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
514   fi
515   if [ $? -ne 0 ]; then
516     if [ "$app_dirname" != "Quit" ]; then
517       echo "Could not locate the application directory: ${app_dirname}"
518     fi
519     return 1
520   fi
521
522   # where we expect to find our checkout folder underneath.
523   full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
524
525   pushd $full_app_dir/$CHECKOUT_DIR_NAME
526 #redundant if pushd  pwd
527 }
528